2012-03-30 17 views
8

Mam kod, który konwertuje parametry variadic na va_list, a następnie przekazuje listę do funkcji, która następnie wywołuje vsnprintf. Działa to dobrze w systemach Windows i OS X, ale nie działa z nieparzystymi wynikami w systemie Linux.va_list złe zachowanie w systemie Linux

W poniższym przykładzie KOD:

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

Wyjście drugiego vsnprintf połączenia wynosi 17, a wynik strcmp wynosi 31; ale ja nie rozumiem dlaczego vsnprintf wróci 17 skoro This is a test. jest 15 znaków, dodaj NULL i masz 16.

Podobne wątki, które widziałam, ale nie rozwiązania tematu:


z odpowiedzi @ Mat za (jestem ponownym użyciem va_list obiekt, który nie jest dozwolony), przychodzi prosto do pierwszego powiązanego wątku, z którym się łączyłem. Więc próbowałem tego kodu:

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

, które per the C99 spec (przypis w punkcie 7.15), powinno działać:

Dozwolone jest tworzenie wskaźnik do va_list i przekazać ten wskaźnik do drugiego funkcja, w którym to przypadku oryginalna funkcja może spowodować dalsze użycie oryginalnej listy po zwróceniu drugiej funkcji.

Ale mój kompilator (gcc 4.4.5 w trybie C99) daje mi ten błąd dotyczący pierwszej linii myPrintfInner:

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

i otrzymaną binarny produkuje dokładnie ten sam efekt, jak po raz pierwszy w okolicy .


Znalazłem to: Is GCC mishandling a pointer to a va_list passed to a function?

Sugerowana obejście (co nie było gwarantowane do pracy, ale nie w praktyce) jest użycie arg_copy pierwszy:

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

W swojej funkcji 'myPrintf' brakuje instrukcji" return ". Spodziewałbym się, że twój kompilator ostrzeże cię o tym. –

+0

bah, humbug! Skopiuj i wklej błąd. –

+1

Twój nowy kod robi dokładnie to samo, co stary: 'original' wskazuje na' params', więc przekazywanie '* original' jest dokładnie takie samo jak przekazywanie' params'. Twoim prawdziwym problemem wydaje się być to, że nie rozumiesz, jak działa 'va_list': są one w zasadzie wskaźnikami do stosu argumentów, a wskaźnik jest zwiększany, gdy jest używany. Więc kiedy dwukrotnie używasz tej samej 'va_list', po raz drugi zwiększasz wskaźnik za końcem listy argumentów. –

Odpowiedz

11

jak zauważa mat, problemem jest to, że jesteś ponownym użyciem va_list. Jeśli nie chcesz, aby zrestrukturyzować swój kod jak on sugeruje, można użyć C99 va_copy() makro, tak:

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

Na kompilatory, które nie obsługują C99, może być w stanie use __va_copy() instead or define your own va_copy() implementation (który będzie nie przenośne, ale zawsze możesz użyć kompilatora/platformy do podsłuchiwania w pliku nagłówkowym, jeśli naprawdę potrzebujesz). Ale tak naprawdę, to już 13 lat, — każdy przyzwoity kompilator powinien obsługiwać C99 w tych dniach, przynajmniej jeśli dasz mu odpowiednie opcje (-std=c99 dla GCC).

+0

Używam GCC w/C99, ale jeśli zobaczysz moje zmienione pytanie, już wypróbowałem to, a wyniki nie są dobre. Wydaje się być zepsuty na x86_64. –

+1

To dziwne. Po prostu wypróbowałem dokładnie kod podany powyżej na Linuxie x86_64 i działa on dla mnie. –

+0

Oto moje dokładne doświadczenie: http://pastebin.ca/2133787 - Nie sądzę, że zrobiłem coś innego niż ty. Jakiej wersji gcc używasz? Zaktualizowałem swój post z tymi informacjami. –

7

Problem polega na tym, że (oprócz brakującej instrukcji return) ponownie używasz parametru va_list bez resetowania go. To nie jest dobrze.

spróbować czegoś jak:

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

(. I włączyć ostrzeżenia kompilatora za)

Nie sądzę przypis wskazaniu oznacza co myślisz robi. Przeczytałem to jako: jeśli przekazujesz bezpośrednio va_list (jako wartość, a nie wskaźnik), jedyną rzeczą, którą możesz zrobić w wywołującym jest va_end to.Ale jeśli przekażesz go jako wskaźnik, możesz na przykład zadzwonić pod numer va_arg, jeśli osoba odwołująca się nie "skonsumuje" wszystkich va_list.

Możesz jednak spróbować z va_copy. Coś jak:

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

Dzięki. Czy możesz zobaczyć mój poprawiony post? –

+0

Edytowałem moją odpowiedź, z inną opcją. – Mat

+0

Works For Me (tm) na x86_64 z GCC, chociaż najpierw "zużywam" kopię. Ale naprawdę sugerowałbym trzymanie się resetowania listy va_list u dzwoniącego. – Mat