Ist es in C möglich, den Aufruf einer variadischen Funktion weiterzuleiten? Wie in,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Das Weiterleiten des Aufrufs auf die oben beschriebene Weise ist in diesem Fall offensichtlich nicht unbedingt erforderlich (da Sie Aufrufe auf andere Weise protokollieren oder vfprintf verwenden könnten), aber für die Codebasis, an der ich arbeite, muss der Wrapper tatsächlich arbeiten, und dies ist nicht der Fall Ich habe (und kann nicht hinzugefügt) eine Hilfsfunktion, die vfprintf ähnelt.
[Update: Es scheint einige Verwirrung zu geben, basierend auf den Antworten, die bisher geliefert wurden. Um die Frage anders auszudrücken: Können Sie im Allgemeinen eine beliebige variable Funktion einschließen, ohne die Definition dieser Funktion zu ändern? ]
Antworten:
Wenn Sie keine analoge Funktion haben
vfprintf
, die eineva_list
statt einer variablen Anzahl von Argumenten verwendet, können Sie dies nicht tun . Sehenhttp://c-faq.com/varargs/handoff.html .Beispiel:
quelle
Nicht direkt, aber es ist üblich (und in der Standardbibliothek ist dies fast überall der Fall), dass verschiedene Funktionen paarweise mit einer
varargs
alternativen Stilfunktion geliefert werden. zBprintf
/vprintf
Die v ... -Funktionen verwenden einen va_list-Parameter, dessen Implementierung häufig mit compilerspezifischer 'Makromagie' erfolgt. Sie können jedoch sicher sein, dass das Aufrufen der v ... style-Funktion von einer variadischen Funktion wie dieser aus funktioniert:
Dies sollte Ihnen den Effekt geben, den Sie suchen.
Wenn Sie eine variable Bibliotheksfunktion schreiben möchten, sollten Sie auch in Betracht ziehen, einen Begleiter im va_list-Stil als Teil der Bibliothek verfügbar zu machen. Wie Sie Ihrer Frage entnehmen können, kann sie sich für Ihre Benutzer als nützlich erweisen.
quelle
va_copy
bevor Sie diemyargs
Variable an eine andere Funktion übergeben. Weitere Informationen finden Sie in MSC39-C , wo angegeben wird, dass das, was Sie tun, undefiniertes Verhalten ist.va_arg()
auf eine ,va_list
die einen unbestimmten Wert hat“, weiß ich nicht tun, weil ich nie verwendenva_arg
in meinem Aufruf Funktion. Der Wert vonmyargs
nach dem Aufruf (in diesem Fall) anvprintf
ist unbestimmt (vorausgesetzt, dass dies bei uns der Fall istva_arg
). Der Standard besagt, dassmyargs
"va_end
vor jeder weiteren Bezugnahme auf [es] an das Makro übergeben werden soll "; Das ist genau das, was ich tue. Ich muss diese Argumente nicht kopieren, da ich nicht beabsichtige, sie in der aufrufenden Funktion zu durchlaufen.ap
an eine Funktion übergeben wird, die verwendet,va_arg(ap,type)
ist der Wert vonap
nach der Rückgabe dieser Funktion undefiniert.C99 unterstützt Makros mit verschiedenen Argumenten . Abhängig von Ihrem Compiler können Sie möglicherweise ein Makro deklarieren, das das tut, was Sie wollen:
Im Allgemeinen ist es jedoch die beste Lösung, die Form va_list der Funktion zu verwenden, die Sie umbrechen möchten, falls eine vorhanden ist.
quelle
Fast mit den Einrichtungen in
<stdarg.h>
:Beachten Sie, dass Sie die
vprintf
Version anstelle von normal verwenden müssenprintf
. In dieser Situation gibt es keine Möglichkeit, eine Variadic-Funktion direkt ohne Verwendung aufzurufenva_list
.quelle
Da es nicht wirklich möglich ist, solche Aufrufe auf nette Weise weiterzuleiten, haben wir dies umgangen, indem wir einen neuen Stapelrahmen mit einer Kopie des ursprünglichen Stapelrahmens eingerichtet haben. Dies ist jedoch höchst unportabel und setzt alle möglichen Annahmen voraus , z. B. dass der Code Frame-Zeiger und die Standardaufrufkonventionen verwendet.
Diese Header-Datei ermöglicht das Umschließen verschiedener Funktionen für x86_64 und i386 (GCC). Es funktioniert nicht für Gleitkomma-Argumente, sollte aber einfach zu erweitern sein, um diese zu unterstützen.
Am Ende können Sie Anrufe wie folgt abschließen:
quelle
Verwenden Sie vfprintf:
quelle
Es gibt keine Möglichkeit, solche Funktionsaufrufe weiterzuleiten, da sich der einzige Speicherort befindet, an dem Sie Rohstapelelemente abrufen können
my_print()
. Der übliche Weg, solche Aufrufe zu verpacken, besteht darin, zwei Funktionen zu haben, eine, die nur die Argumente in die verschiedenenvarargs
Strukturen konvertiert, und eine andere, die diese Strukturen tatsächlich verarbeitet. Mit einem solchen Doppelfunktionsmodell können Sie (zum Beispiel)printf()
umbrechen, indem Sie die Strukturenmy_printf()
mit initialisierenva_start()
und dann an übergebenvfprintf()
.quelle
Ja, Sie können es tun, aber es ist etwas hässlich und Sie müssen die maximale Anzahl von Argumenten kennen. Wenn Sie sich in einer Architektur befinden, in der die Argumente nicht wie x86 (z. B. PowerPC) auf dem Stapel übergeben werden, müssen Sie außerdem wissen, ob und ob "spezielle" Typen (double, float, altivec usw.) verwendet werden Gehen Sie also entsprechend mit ihnen um. Es kann schnell schmerzhaft sein, aber wenn Sie auf x86 arbeiten oder wenn die ursprüngliche Funktion einen genau definierten und begrenzten Umfang hat, kann es funktionieren. Es wird immer noch ein Hack sein , verwenden Sie es zum Debuggen. Erstellen Sie keine Software dafür. Hier ist ein funktionierendes Beispiel für x86:
Aus irgendeinem Grund können Sie Floats nicht mit va_arg verwenden. Laut gcc werden sie in double konvertiert, aber das Programm stürzt ab. Das allein zeigt, dass diese Lösung ein Hack ist und dass es keine allgemeine Lösung gibt. In meinem Beispiel habe ich angenommen, dass die maximale Anzahl von Argumenten 8 beträgt, aber Sie können diese Anzahl erhöhen. Die umschlossene Funktion verwendete ebenfalls nur Ganzzahlen, funktioniert jedoch genauso mit anderen 'normalen' Parametern, da diese immer in Ganzzahlen umgewandelt werden. Die Zielfunktion kennt ihre Typen, Ihr Zwischen-Wrapper muss dies jedoch nicht. Der Wrapper muss auch nicht die richtige Anzahl von Argumenten kennen, da die Zielfunktion dies auch weiß. Um nützliche Arbeit zu leisten (außer nur den Anruf zu protokollieren), müssen Sie wahrscheinlich beide kennen.
quelle
Grundsätzlich gibt es drei Möglichkeiten.
Eine besteht darin, es nicht weiterzugeben, sondern die variable Implementierung Ihrer Zielfunktion zu verwenden und die Ellipsen nicht weiterzugeben. Das andere ist die Verwendung eines variadischen Makros. Die dritte Option ist alles, was mir fehlt.
Normalerweise entscheide ich mich für Option eins, da ich der Meinung bin, dass dies sehr einfach zu handhaben ist. Option zwei hat einen Nachteil, da das Aufrufen verschiedener Makros einige Einschränkungen aufweist.
Hier ist ein Beispielcode:
quelle
Der beste Weg, dies zu tun, ist
quelle
gcc bietet eine Erweiterung, die dies kann:
__builtin_apply
und Verwandte. Siehe Erstellen von Funktionsaufrufen im gcc-Handbuch.Ein Beispiel:
Probieren Sie es auf Godbolt
In der Dokumentation gibt es einige Vorsichtsmaßnahmen, dass dies in komplizierteren Situationen möglicherweise nicht funktioniert. Und Sie müssen eine maximale Größe für die Argumente fest codieren (hier habe ich 1000 verwendet). Es könnte jedoch eine vernünftige Alternative zu den anderen Ansätzen sein, bei denen der Stapel entweder in C- oder in Assemblersprache zerlegt wird.
quelle
Ich bin mir nicht sicher, ob dies zur Beantwortung der Frage von OP beiträgt, da ich nicht weiß, warum die Einschränkung für die Verwendung einer Hilfsfunktion ähnlich vfprintf in der Wrapper-Funktion gilt. Ich denke, das Hauptproblem hierbei ist, dass es schwierig ist, die Liste der variadischen Argumente weiterzuleiten, ohne sie zu interpretieren. Es ist möglich, die Formatierung durchzuführen (unter Verwendung einer Hilfsfunktion ähnlich vfprintf: vsnprintf) und die formatierte Ausgabe mit variadischen Argumenten an die umschlossene Funktion weiterzuleiten (dh die Definition der umschlossenen Funktion nicht zu ändern). Auf geht's:
Ich bin hier auf diese Lösung gestoßen .
quelle