2012-03-30 13 views
8

Ho un codice che converte i parametri variadici in un va_list, quindi passa l'elenco su una funzione che chiama quindi vsnprintf. Funziona bene su Windows e OS X, ma non funziona con risultati strani su Linux.comportamento scorretto va_list su Linux

Nel seguente esempio di codice:

#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; 
} 

L'uscita della seconda vsnprintf chiamata è 17, e il risultato di strcmp è 31; ma io non capisco perché vsnprintf sarebbero tornati 17 visto che This is a test. è di 15 caratteri, aggiungere il NULL e si ottiene 16.

discussioni correlate che ho visto, ma non affrontano il tema:


Con risposta @ di Mat (sto riutilizzare laOggetto, che non è consentito), questo viene esattamente intorno al primo thread correlato a cui mi sono collegato. Così ho tentato questo codice invece:

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; 
} 

Che, per the C99 spec (nota in calce alla Sezione 7.15), dovrebbe funzionare:

È consentito di creare un puntatore ad una va_list e superare tale puntatore ad un altro funzione, nel qual caso la funzione originale potrebbe rendere ulteriore uso della lista originale dopo che l'altra funzione è tornata.

Ma il mio compilatore (gcc 4.4.5 in modalità C99) mi dà questo errore per quanto riguarda la prima linea di myPrintfInner:

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

E il binario risultante produce lo stesso effetto esattamente come la prima volta intorno .


trovato questo: Is GCC mishandling a pointer to a va_list passed to a function?

La soluzione suggerita (che non è stato garantito il funzionamento, ma ha fatto in pratica) è quello di utilizzare arg_copy prima:

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

La funzione 'myPrintf' manca un'istruzione' return'. Mi sarei aspettato che il tuo compilatore ti avvisasse di questo. –

+0

bah, humbug! Copia e incolla errore. –

+1

Il tuo nuovo codice fa esattamente la stessa cosa del vecchio: 'original' punta a' params', quindi passare '* original' è esattamente come passare' params'. Il tuo vero problema sembra essere che non capisci come funzionano 'va_list: sono essenzialmente dei puntatori allo stack degli argomenti e il puntatore viene incrementato man mano che viene usato. Quindi, quando si utilizza lo stesso 'va_list' due volte, la seconda volta si incrementa il puntatore oltre la fine dell'elenco degli argomenti. –

risposta

11

Come nota Mat, il problema è che si sta riutilizzando il va_list. Se non si vuole ristrutturare il vostro codice come egli suggerisce, è possibile utilizzare la macro C99 va_copy(), in questo modo:

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; 
} 

su compilatori che non supportano C99, sarai in grado use __va_copy() instead or define your own va_copy() implementation (che sarà non portabile, ma puoi sempre usare il compilatore/piattaforma sniffare in un file di intestazione se davvero ne hai bisogno). Ma davvero, sono passati 13 anni — ogni compilatore decente dovrebbe supportare C99 in questi giorni, almeno se gli dai le opzioni giuste (-std=c99 per GCC).

+0

Sto usando GCC w/C99, ma se vedrete la mia domanda rivista, ho già provato questo e i risultati non sono buoni. Sembra essere rotto su x86_64. –

+1

È strano. Ho appena provato il codice esatto che ho dato sopra su x86_64 Linux e funziona per me. –

+0

Ecco la mia esperienza esatta: http://pastebin.ca/2133787 - Non penso di aver fatto qualcosa di diverso da te. Quale versione di gcc stai usando? Ho aggiornato il mio post con queste informazioni. –

7

Il problema è che (a parte la dichiarazione di reso mancante) stai riutilizzando il parametro va_list senza reimpostarlo. Questo non è buono.

provare qualcosa di simile:

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; 
} 

(. E accendere gli avvertimenti del compilatore)

Non credo che la nota in calce si punta significa quello che pensi lo fa. L'ho letto come: se si passa direttamente a va_list (come valore, non come puntatore), l'unica cosa che si può fare nel chiamante è va_end.Ma se lo si passa come puntatore, è possibile, ad esempio, chiamare va_arg nel chiamante se il destinatario non "consuma" tutti gli va_list.

Si potrebbe provare con va_copy però. Qualcosa di simile:

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

Grazie. Puoi vedere il mio post rivisto? –

+0

Modificato la mia risposta, con un'altra opzione. – Mat

+0

Works For Me (tm) su x86_64 con GCC, anche se sto "consumando" prima la copia. Ma suggerirei di attenermi al reset della va_list nel chiamante. – Mat