Czy w C można przekazać wywołanie funkcji wariadycznej? Jak w,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Przekazywanie wywołania w powyższy sposób oczywiście nie jest absolutnie konieczne w tym przypadku (ponieważ można rejestrować wywołania w inny sposób lub użyć vfprintf), ale podstawa kodu, nad którą pracuję, wymaga od opakowania, aby wykonała jakąś rzeczywistą pracę, i nie wykonuje nie ma (i nie mógł dodać) funkcji pomocniczej podobnej do vfprintf.
[Aktualizacja: wydaje się, że istnieje pewne zamieszanie w oparciu o odpowiedzi, które zostały dostarczone do tej pory. Aby sformułować pytanie w inny sposób: ogólnie, czy można owinąć dowolną funkcję wariadyczną bez zmiany jej definicji .]
Odpowiedzi:
Jeśli nie masz funkcji analogicznej do
vfprintf
tej, która pobierava_list
zmienną liczbę argumentów, nie możesz tego zrobić . Widziećhttp://c-faq.com/varargs/handoff.html .Przykład:
źródło
Nie bezpośrednio, jednak powszechne (i prawie tak samo jest w standardowej bibliotece), że funkcje variadic występują w parach z
varargs
alternatywną funkcją stylu. np.printf
/vprintf
Funkcje v ... pobierają parametr va_list, którego implementacja często odbywa się za pomocą specyficznej dla kompilatora „magii makro”, ale masz gwarancję, że wywołanie funkcji stylu v ... z funkcji variadic, takiej jak ta, zadziała:
To powinno dać efekt, którego szukasz.
Jeśli zastanawiasz się nad napisaniem funkcji biblioteki variadic, powinieneś również rozważyć udostępnienie elementu towarzyszącego w stylu va_list jako części biblioteki. Jak widać z pytania, może okazać się przydatne dla użytkowników.
źródło
va_copy
przed przekazaniemmyargs
zmiennej do innej funkcji. Proszę zobaczyć MSC39-C , gdzie stwierdza, że to, co robisz, jest niezdefiniowanym zachowaniem.va_arg()
elementuva_list
o nieokreślonej wartości”, nie robię tego, ponieważ nigdy nie używamva_arg
funkcji wywoływania. Wartość odmyargs
po wywołaniu (w tym przypadku) dovprintf
jest nieokreślona (przy założeniu, że robi się z namiva_arg
). Norma mówi, żemyargs
„przekazuje sięva_end
makro przed dalszym odniesieniem do [it]”; co dokładnie robię. Nie muszę kopiować tych argumentów, ponieważ nie mam zamiaru iterować ich w funkcji wywołującej.ap
zostanie przekazana do funkcji, która używa,va_arg(ap,type)
wartośćap
po jej zwróceniu jest niezdefiniowana.C99 obsługuje makra z argumentami variadic ; w zależności od kompilatora możesz zadeklarować makro, które robi to, co chcesz:
Ogólnie jednak najlepszym rozwiązaniem jest użycie postaci va_list funkcji, którą próbujesz zawinąć, jeśli taka istnieje.
źródło
Prawie korzystając z udogodnień dostępnych w
<stdarg.h>
:Pamiętaj, że będziesz musiał użyć
vprintf
wersji zamiast zwykłegoprintf
. W tej sytuacji nie ma sposobu bezpośredniego wywołania funkcji variadic bez użyciava_list
.źródło
Ponieważ tak naprawdę nie jest możliwe przekazywanie takich wywołań w przyjemny sposób, obejrzeliśmy to, ustawiając nową ramkę stosu z kopią oryginalnej ramki stosu. Jest to jednak wysoce nieprzenośne i przyjmuje wszelkie założenia , np. Że kod używa wskaźników ramek i „standardowych” konwencji wywoływania.
Ten plik nagłówka pozwala na zawijanie różnych funkcji dla x86_64 i i386 (GCC). Nie działa w przypadku argumentów zmiennoprzecinkowych, ale powinien być prosty do rozszerzenia o obsługę tych argumentów.
Na koniec możesz zawijać takie połączenia:
źródło
Użyj vfprintf:
źródło
Nie ma możliwości przekazania takich wywołań funkcji, ponieważ znajduje się jedyne miejsce, w którym można pobrać surowe elementy stosu
my_print()
. Zwykłym sposobem na zawijanie takich wywołań jest posiadanie dwóch funkcji, jednej, która po prostu konwertuje argumenty na różnevarargs
struktury, a drugiej, która faktycznie działa na tych strukturach. Korzystając z takiego modelu dwufunkcyjnego, możesz (na przykład) zawinąćprintf()
, inicjując struktury zamy_printf()
pomocąva_start()
, a następnie przekazać je dovfprintf()
.źródło
Tak, możesz to zrobić, ale jest to trochę brzydkie i musisz znać maksymalną liczbę argumentów. Ponadto, jeśli masz architekturę, w której argumenty nie są przekazywane na stosie, takie jak x86 (na przykład PowerPC), będziesz musiał wiedzieć, czy są używane typy „specjalne” (podwójne, zmiennoprzecinkowe, altivec itp.) I czy więc radzcie sobie z nimi odpowiednio. Może to być szybko bolesne, ale jeśli używasz x86 lub jeśli oryginalna funkcja ma dobrze zdefiniowany i ograniczony obwód, może działać. To nadal będzie hack , użyj go do celów debugowania. Nie buduj wokół tego oprogramowania. Tak czy inaczej, oto działający przykład na x86:
Z jakiegoś powodu nie można używać liczb zmiennoprzecinkowych z va_arg, gcc mówi, że są one konwertowane na podwójne, ale program ulega awarii. Samo to pokazuje, że to rozwiązanie jest włamaniem i że nie ma ogólnego rozwiązania. W moim przykładzie założyłem, że maksymalna liczba argumentów wynosiła 8, ale można zwiększyć tę liczbę. Funkcja zawinięcia również używała tylko liczb całkowitych, ale działa tak samo z innymi „normalnymi” parametrami, ponieważ zawsze rzutują na liczby całkowite. Funkcja docelowa zna ich typy, ale opakowanie pośrednie nie musi tego robić. Opakowanie również nie musi znać odpowiedniej liczby argumentów, ponieważ funkcja docelowa również to zna. Aby wykonać przydatną pracę (oprócz rejestrowania połączenia), prawdopodobnie będziesz musiał znać oba.
źródło
gcc oferuje rozszerzenie, które może to zrobić:
__builtin_apply
i krewnych. Zobacz: Konstruowanie wywołań funkcji w podręczniku gcc.Przykład:
Wypróbuj na Godbolt
Dokumentacja zawiera pewne ostrzeżenia, że może nie działać w bardziej skomplikowanych sytuacjach. I musisz ustalić na stałe maksymalny rozmiar argumentów (tutaj użyłem 1000). Ale może to być rozsądna alternatywa dla innych podejść, które polegają na analizie stosu w języku C lub asemblerze.
źródło
Istnieją zasadniczo trzy opcje.
Jednym z nich jest nie przekazywanie go, ale stosowanie implementacji variadic funkcji docelowej i nie przekazywanie elips. Drugim jest użycie makra variadic. Trzecia opcja to wszystko, czego mi brakuje.
Zwykle wybieram opcję pierwszą, ponieważ wydaje mi się, że jest to naprawdę łatwe w obsłudze. Opcja druga ma wadę, ponieważ istnieją pewne ograniczenia w wywoływaniu makr variadic.
Oto przykładowy kod:
źródło
Najlepszym sposobem na to jest
źródło
Nie jestem pewien, czy to pomaga odpowiedzieć na pytanie OP, ponieważ nie wiem, dlaczego obowiązuje ograniczenie używania funkcji pomocniczej podobnej do vfprintf w funkcji opakowania. Myślę, że kluczowym problemem jest to, że przekazywanie listy argumentów variadic bez ich interpretacji jest trudne. Możliwe jest przeprowadzenie formatowania (za pomocą funkcji pomocniczej podobnej do vfprintf: vsnprintf) i przesłanie sformatowanego wyjścia do zawiniętej funkcji z argumentami variadic (tj. Bez modyfikacji definicji zawiniętej funkcji). Więc zaczynamy:
Natrafiłem tutaj na to rozwiązanie .
źródło