Weiterleiten eines Aufrufs einer Variadic-Funktion in C
Lesezeit: 8 Minuten
Patrick
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 obige Weise ist in diesem Fall offensichtlich nicht unbedingt erforderlich (da Sie Aufrufe auf andere Weise protokollieren oder vfprintf verwenden könnten), aber die Codebasis, an der ich arbeite, erfordert, dass der Wrapper tatsächlich etwas tut, und tut dies auch Ich habe keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).
[Update: there seems to be some confusion based on the answers that have been supplied so far. To phrase the question another way: in general, can you wrap some arbitrary variadic function without modifying that function’s definition.]
tolle Frage – glücklicherweise kann ich eine vFunc-Überladung hinzufügen, die eine va_list benötigt. Danke fürs Schreiben.
– Gishu
3. Juni 2009 um 7:16 Uhr
Adam Rosenfeld
Wenn Sie keine Funktion analog zu haben vfprintf das dauert ein va_list statt einer variablen Anzahl von Argumenten, du kannst es nicht. Sehen http://c-faq.com/varargs/handoff.html.
Je nachdem, wie wichtig das Problem ist, bietet der Aufruf durch Inline-Assemblierung diese Möglichkeit immer noch.
– Philipp
11. Januar 2015 um 2:03 Uhr
Mindestens ein Argument muss übergeben werden, myfun("") wird nicht unterstützt – gibt es eine Lösung, wie ich das beheben kann? (MSVC druckt: zu wenige Argumente für Aufruf)
– mvorisek
20. Juli 2020 um 17:28 Uhr
@mvorisek: Nein, dafür gibt es keine genau definierte Lösung, da variadische Funktionen mindestens einen benannten Parameter vor dem nehmen müssen .... Ihre Problemumgehung scheint möglicherweise mit Ihrem speziellen Compiler zu funktionieren, aber es ist immer noch undefiniertes Verhalten gemäß dem Sprachstandard.
Nicht direkt, aber es ist üblich (und Sie finden fast überall den Fall in der Standardbibliothek), dass variadische Funktionen paarweise mit a kommen varargs Stil alternative Funktion. z.B printf/vprintf
Die v…-Funktionen nehmen einen va_list-Parameter, dessen Implementierung oft mit Compiler-spezifischer “Makro-Magie” erfolgt, aber Sie können sicher sein, dass das Aufrufen der v…-Style-Funktion aus einer variadischen Funktion wie dieser funktioniert:
#include <stdarg.h>
int m_printf(char *fmt, ...)
{
int ret;
/* Declare a va_list type variable */
va_list myargs;
/* Initialise the va_list variable with the ... after fmt */
va_start(myargs, fmt);
/* Forward the '...' to vprintf */
ret = vprintf(fmt, myargs);
/* Clean up the va_list */
va_end(myargs);
return ret;
}
Dies sollte Ihnen den Effekt geben, den Sie suchen.
Wenn Sie erwägen, eine variadische Bibliotheksfunktion zu schreiben, sollten Sie auch erwägen, einen va_list-Stilbegleiter als Teil der Bibliothek verfügbar zu machen. Wie Sie Ihrer Frage entnehmen können, kann es sich für Ihre Benutzer als nützlich erweisen.
Ich bin mir ziemlich sicher, dass Sie anrufen müssen va_copy vor dem Passieren der myargs Variable zu einer anderen Funktion. Bitte sehen MSC39-Cwo es besagt, dass das, was Sie tun, undefiniertes Verhalten ist.
– Avigiano
26. Februar 2016 um 4:23 Uhr
@aviggiano: Die Regel lautet “Nicht anrufen va_arg() auf einen va_list das einen unbestimmten Wert hat”, mache ich nicht, weil ich es nie benutze va_arg in meiner aufrufenden Funktion. Das Wert von myargs nach dem Anruf (in diesem Fall) an vprintf ist unbestimmt (vorausgesetzt, es tut uns va_arg). Das sagt die Norm myargs „wird an die weitergegeben va_end Makro vor jedem weiteren Verweis auf [it]”; Das ist genau das, was ich tue. Ich muss diese Argumente nicht kopieren, weil ich nicht die Absicht habe, sie in der aufrufenden Funktion zu durchlaufen.
– CB Bailey
26. Februar 2016 um 15:16 Uhr
Sie haben Recht. Das Linux-Mann Seite bestätigt das. Wenn ap wird an eine Funktion übergeben, die verwendet va_arg(ap,type) dann der Wert von ap ist nach der Rückkehr dieser Funktion undefiniert.
– Avigiano
26. Februar 2016 um 16:28 Uhr
Commodore Jäger
C99 unterstützt Makros mit variadischen Argumenten; Abhängig von Ihrem Compiler können Sie möglicherweise ein Makro deklarieren, das das tut, was Sie wollen:
#define my_printf(format, ...) \
do { \
fprintf(stderr, "Calling printf with fmt %s\n", format); \
some_other_variadac_function(format, ##__VA_ARGS__); \
} while(0)
Im Allgemeinen ist die beste Lösung jedoch die Verwendung von va_liste Form der Funktion, die Sie umschließen möchten, falls eine vorhanden ist.
Da es nicht wirklich möglich ist, solche Anrufe 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 untragbar und macht alle möglichen AnnahmenzB dass der Code Frame-Zeiger und die ‘Standard’-Aufrufkonventionen verwendet.
Diese Header-Datei ermöglicht es, verschiedene Funktionen für x86_64 und i386 (GCC) zu umschließen. Es funktioniert nicht für Gleitkommaargumente, sollte aber einfach zu erweitern sein, um diese zu unterstützen.
#ifndef _VA_ARGS_WRAPPER_H
#define _VA_ARGS_WRAPPER_H
#include <limits.h>
#include <stdint.h>
#include <alloca.h>
#include <inttypes.h>
#include <string.h>
/* This macros allow wrapping variadic functions.
* Currently we don't care about floating point arguments and
* we assume that the standard calling conventions are used.
*
* The wrapper function has to start with VA_WRAP_PROLOGUE()
* and the original function can be called by
* VA_WRAP_CALL(function, ret), whereas the return value will
* be stored in ret. The caller has to provide ret
* even if the original function was returning void.
*/
#define __VA_WRAP_CALL_FUNC __attribute__ ((noinline))
#define VA_WRAP_CALL_COMMON() \
uintptr_t va_wrap_this_bp,va_wrap_old_bp; \
va_wrap_this_bp = va_wrap_get_bp(); \
va_wrap_old_bp = *(uintptr_t *) va_wrap_this_bp; \
va_wrap_this_bp += 2 * sizeof(uintptr_t); \
size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \
uintptr_t *va_wrap_stack = alloca(va_wrap_size); \
memcpy((void *) va_wrap_stack, \
(void *)(va_wrap_this_bp), va_wrap_size);
#if ( __WORDSIZE == 64 )
/* System V AMD64 AB calling convention */
static inline uintptr_t __attribute__((always_inline))
va_wrap_get_bp()
{
uintptr_t ret;
asm volatile ("mov %%rbp, %0":"=r"(ret));
return ret;
}
#define VA_WRAP_PROLOGUE() \
uintptr_t va_wrap_ret; \
uintptr_t va_wrap_saved_args[7]; \
asm volatile ( \
"mov %%rsi, (%%rax)\n\t" \
"mov %%rdi, 0x8(%%rax)\n\t" \
"mov %%rdx, 0x10(%%rax)\n\t" \
"mov %%rcx, 0x18(%%rax)\n\t" \
"mov %%r8, 0x20(%%rax)\n\t" \
"mov %%r9, 0x28(%%rax)\n\t" \
: \
:"a"(va_wrap_saved_args) \
);
#define VA_WRAP_CALL(func, ret) \
VA_WRAP_CALL_COMMON(); \
va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack; \
asm volatile ( \
"mov (%%rax), %%rsi \n\t" \
"mov 0x8(%%rax), %%rdi \n\t" \
"mov 0x10(%%rax), %%rdx \n\t" \
"mov 0x18(%%rax), %%rcx \n\t" \
"mov 0x20(%%rax), %%r8 \n\t" \
"mov 0x28(%%rax), %%r9 \n\t" \
"mov $0, %%rax \n\t" \
"call *%%rbx \n\t" \
: "=a" (va_wrap_ret) \
: "b" (func), "a" (va_wrap_saved_args) \
: "%rcx", "%rdx", \
"%rsi", "%rdi", "%r8", "%r9", \
"%r10", "%r11", "%r12", "%r14", \
"%r15" \
); \
ret = (typeof(ret)) va_wrap_ret;
#else
/* x86 stdcall */
static inline uintptr_t __attribute__((always_inline))
va_wrap_get_bp()
{
uintptr_t ret;
asm volatile ("mov %%ebp, %0":"=a"(ret));
return ret;
}
#define VA_WRAP_PROLOGUE() \
uintptr_t va_wrap_ret;
#define VA_WRAP_CALL(func, ret) \
VA_WRAP_CALL_COMMON(); \
asm volatile ( \
"mov %2, %%esp \n\t" \
"call *%1 \n\t" \
: "=a"(va_wrap_ret) \
: "r" (func), \
"r"(va_wrap_stack) \
: "%ebx", "%ecx", "%edx" \
); \
ret = (typeof(ret))va_wrap_ret;
#endif
#endif
Am Ende können Sie Anrufe wie folgt umbrechen:
int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...)
{
VA_WRAP_PROLOGUE();
int ret;
VA_WRAP_CALL(printf, ret);
printf("printf returned with %d \n", ret);
return ret;
}
Fast, mit den verfügbaren Einrichtungen in <stdarg.h>:
#include <stdarg.h>
int my_printf(char *format, ...)
{
va_list args;
va_start(args, format);
int r = vprintf(format, args);
va_end(args);
return r;
}
Beachten Sie, dass Sie die verwenden müssen vprintf Version statt einfach printf. Es gibt keine Möglichkeit, eine variadische Funktion in dieser Situation direkt aufzurufen, ohne zu verwenden va_list.
Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).
– Patrick
29. September 2008 um 20:53 Uhr
Nate Eldredge
gcc bietet eine Erweiterung an, die dies tun kann: __builtin_apply und Verwandte. Sehen Aufbau von Funktionsaufrufen im gcc-Handbuch.
Ein Beispiel:
#include <stdio.h>
int my_printf(const char *fmt, ...) {
void *args = __builtin_apply_args();
printf("Hello there! Format string is %s\n", fmt);
void *ret = __builtin_apply((void (*)())printf, args, 1000);
__builtin_return(ret);
}
int main(void) {
my_printf("%d %f %s\n", -37, 3.1415, "spam");
return 0;
}
Es gibt einige Vorsichtshinweise in der Dokumentation, dass es 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). Aber es könnte eine vernünftige Alternative zu den anderen Ansätzen sein, bei denen der Stack entweder in C oder in der Assemblersprache zerlegt wird.
Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).
– Patrick
29. September 2008 um 20:53 Uhr
Greg Rogers
Verwenden Sie vfprintf:
int my_printf(char *fmt, ...) {
va_list va;
int ret;
va_start(va, fmt);
ret = vfprintf(stderr, fmt, va);
va_end(va);
return ret;
}
Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).
– Patrick
29. September 2008 um 20:53 Uhr
14260300cookie-checkWeiterleiten eines Aufrufs einer Variadic-Funktion in Cyes
tolle Frage – glücklicherweise kann ich eine vFunc-Überladung hinzufügen, die eine va_list benötigt. Danke fürs Schreiben.
– Gishu
3. Juni 2009 um 7:16 Uhr