2012-03-30 17 views
8

Ich habe einige Code, der variadic Parameter in eine va_list konvertiert, dann übergibt die Liste an eine Funktion, die dann ruft vsnprintf. Dies funktioniert unter Windows und OS X gut, aber es schlägt mit seltsamen Ergebnissen unter Linux fehl.va_list Fehlverhalten unter Linux

Im folgenden Codebeispiel:

#include <string.h> 
#include <stdio.h> 
#include <stdarg.h> 
#include <stdlib.h> 

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list *original = &params; 
    size_t length = vsnprintf(NULL, 0, message, *original); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

char *myPrintf(const char *message, ...) 
{ 
    va_list va_args; 
    va_start(va_args, message); 

    size_t length = vsnprintf(NULL, 0, message, va_args); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, va_args); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    va_end(va_args); 

    return final; 
} 

int main(int argc, char **argv) 
{ 
    char *test = myPrintf("This is a %s.", "test"); 
    char *actual = "This is a test."; 
    int result = strcmp(test, actual); 

    if (result != 0) 
    { 
     printf("%d: Test failure!\r\n", result); 
    } 
    else 
    { 
     printf("Test succeeded.\r\n"); 
    } 

    return 0; 
} 

Der Ausgang des zweiten vsnprintf Anruf 17, und das Ergebnis der strcmp ist 31; aber ich nicht, warum vsnprintf 17 da This is a test. 15 Zeichen zurückkehren würde, fügen Sie den NULL und Sie erhalten 16

verwandte Themen, die ich gesehen habe, aber befassen sich nicht mit dem Thema:


Mit Antwort des @ Mat (ich bin die Wiederverwendung der va_list Objekt, was nicht erlaubt ist), das kommt direkt zum ersten verwandten Thread, mit dem ich verlinkt bin. Also habe ich versucht, diesen Code statt:

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list *original = &params; 
    size_t length = vsnprintf(NULL, 0, message, params); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, *original); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

Which, per the C99 spec (Fußnote in Abschnitt 7.15), funktionieren sollte:

Es erlaubt ist, einen Zeiger auf eine va_list zu erstellen und diesen Zeiger zu einem anderen übergeben Funktion, in diesem Fall kann die ursprüngliche Funktion weitere Verwendung der ursprünglichen Liste nach der anderen Funktion zurückgeben.

Aber mein Compiler (gcc 4.4.5 in C99-Modus) gibt mir diesen Fehler in Bezug auf die erste Zeile des myPrintfInner:

test.c: In function ‘myPrintfInner’: 
test.c:8: warning: initialization from incompatible pointer type 

und das resultierende binäre erzeugt genau die gleiche Wirkung wie das erste Mal .


Gefunden dies: Is GCC mishandling a pointer to a va_list passed to a function?

Die vorgeschlagene Abhilfe (die nicht zur Arbeit war garantiert, aber in der Praxis hat) ist arg_copy zu verwenden zuerst:

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list args_copy; 
    va_copy(args_copy, params); 

    size_t length = vsnprintf(NULL, 0, message, params); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, args_copy); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 
+0

Ihre 'myPrintf' -Funktion fehlt eine' return' Anweisung. Ich hätte erwartet, dass dein Compiler dich davor warnen würde. –

+0

bah, humbug! Fehler beim Kopieren und Einfügen –

+1

Ihr neuer Code tut genau das gleiche wie der alte: 'original' zeigt auf' params', also ist das Übergeben von 'original' genau das gleiche wie das Übergeben von' params'. Ihr wirkliches Problem scheint zu sein, dass Sie nicht verstehen, wie 'va_list' funktioniert: Sie sind im Wesentlichen Zeiger auf den Argument-Stack, und der Zeiger wird erhöht, wenn er benutzt wird. Wenn Sie die gleiche 'va_list' zweimal verwenden, erhöhen Sie den Zeiger das zweite Mal nach dem Ende der Argumentliste. –

Antwort

11

Wie Mat Noten, das Problem ist, dass Sie die va_list sind die Wiederverwendung. Wenn Sie wollen Ihren Code nicht neu strukturieren, wie er schon sagt, können Sie den C99 va_copy() Makro wie folgt verwenden:

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list copy; 

    va_copy(copy, params); 
    size_t length = vsnprintf(NULL, 0, message, copy); 
    va_end(copy); 

    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

Auf Compiler, die C99 nicht unterstützen, können Sie in der Lage sein use __va_copy() instead or define your own va_copy() implementation (welche sein nicht portabel, aber Sie können immer Compiler/Plattform-Sniffing in einer Header-Datei verwenden, wenn Sie wirklich brauchen). Aber wirklich, es war 13 Jahre lang — jeder ordentliche Compiler sollte C99 in diesen Tagen unterstützen, zumindest wenn Sie ihm die richtigen Optionen geben (-std=c99 für GCC).

+0

Ich benutze GCC w/C99, aber wenn Sie meine überarbeitete Frage sehen, habe ich dies bereits versucht und die Ergebnisse sind nicht gut. Scheint gebrochen zu sein auf x86_64. –

+1

Das ist seltsam. Ich habe gerade den genauen Code, den ich oben auf x86_64 Linux gegeben habe, ausprobiert und es funktioniert für mich. –

+0

Hier ist meine genaue Erfahrung: http://pastebin.ca/2133787 - Ich glaube nicht, dass ich anders als Sie. Welche Version von gcc verwendest du? Ich habe meinen Beitrag mit dieser Information aktualisiert. –

7

Das Problem ist, dass (Abgesehen von der fehlenden return-Anweisung verwenden Sie den Parameter va_list erneut, ohne ihn zurückzusetzen. Das ist nicht gut.

Probieren Sie etwas wie:

size_t myPrintfInnerLen(const char *message, va_list params) 
{ 
    return vsnprintf(NULL, 0, message, params); 
} 

char *myPrintfInner(size_t length, const char *message, va_list params) 
{ 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

char *myPrintf(const char *message, ...) 
{ 
    va_list va_args; 
    va_start(va_args, message); 
    size_t length = myPrintfInnerLen(message, va_args); 
    va_end(va_args); 
    va_start(va_args, message); 
    char *ret = myPrintfInner(length, message, va_args); 
    va_end(va_args); 
    return ret; 
} 

(. Und schalten Sie Warnungen Ihre Compiler)

ich die Fußnote Sie zeigen nicht denken, bedeutet das, was Sie denken, es tut. Ich lese es als: Wenn Sie eine va_list direkt übergeben (als Wert, kein Zeiger), ist das einzige, was Sie in dem Aufrufer tun können, va_end es.Aber wenn Sie es als Zeiger übergeben, könnten Sie sagen, va_arg im Aufrufer aufrufen, wenn der Angerufene nicht alle va_list "konsumiert".

Sie könnten versuchen mit va_copy obwohl. Etwas wie:

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list temp; 
    va_copy(temp, params); 
    size_t length = vsnprintf(NULL, 0, message, temp); 
    ... 
+0

Danke. Kannst du bitte meinen überarbeiteten Beitrag sehen? –

+0

Bearbeitete meine Antwort mit einer anderen Option. – Mat

+0

Works For Me (tm) auf x86_64 mit GCC, obwohl ich die Kopie zuerst "konsumiere". Aber ich würde wirklich vorschlagen, beim Zurücksetzen der va_list im Anrufer zu bleiben. – Mat