2012-03-30 15 views
8

Tengo un código que convierte parámetros variados en un va_list, luego pasa la lista a una función que luego llama a vsnprintf. Esto funciona bien en Windows y OS X, pero está fallando con resultados extraños en Linux.va_list mal comportamiento en Linux

En el siguiente ejemplo de código:

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

La salida del segundo vsnprintf llamada es 17, y el resultado de strcmp es 31; pero yo no entiendo por qué vsnprintf regresarían 17 This is a test. ya que tiene 15 caracteres, añadir el NULL y se obtiene 16.

hilos relacionados que he visto, pero no abordan el tema:


con la respuesta de @ Mat (estoy reutilizando el va_list objeto, que no está permitido), esto viene directamente al primer hilo relacionado al que me he vinculado. Así que he tratado este código en su lugar:

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

cual, per the C99 spec (nota al pie en la Sección 7.15), debería funcionar:

Está permitido crear un puntero a una va_list y pasar ese puntero a otro función, en cuyo caso la función original puede hacer uso posterior de la lista original después de que la otra función regrese.

Pero mi compilador (gcc 4.4.5 en modo C99) me da este error con respecto a la primera línea de myPrintfInner:

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

Y el binario resultante produce el mismo efecto que en la primera ronda .


encontrado esto: Is GCC mishandling a pointer to a va_list passed to a function?

La solución sugerida (que no se garantiza que funcione, pero sí en la práctica) es el uso de arg_copy primera:

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

Su función 'myPrintf' le falta una declaración' return'. Esperaba que tu compilador te advirtiera sobre eso. –

+0

bah, humbug! Copiar y pegar el error. –

+1

Su nuevo código está haciendo exactamente lo mismo que el anterior: 'original' apunta a' params', por lo que pasar '* original' es exactamente lo mismo que pasar' params'. Su verdadero problema parece ser que no entiende cómo funciona 'va_list': son esencialmente punteros a la pila de argumentos, y el puntero se incrementa a medida que se usa. Entonces, cuando usa el mismo 'va_list' dos veces, la segunda vez está incrementando el puntero más allá del final de la lista de argumentos. –

Respuesta

11

Como notas Mat, el problema es que se está reutilizando el va_list. Si no desea reestructurar su código como él sugiere, se puede utilizar la macro C99 va_copy(), así:

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

En los compiladores que no soportan C99, es posible que pueda use __va_copy() instead or define your own va_copy() implementation (que será no portátil, pero siempre puede usar el rastreo de compilador/plataforma en un archivo de encabezado si realmente lo necesita). Pero en realidad, han pasado 13 años —, cualquier compilador decente debería ser compatible con C99 estos días, al menos si le da las opciones correctas (-std=c99 para GCC).

+0

Estoy usando GCC w/C99, pero si verá mi pregunta revisada, ya lo he intentado y los resultados no son buenos. Parece estar roto en x86_64. –

+1

Eso es extraño. Acabo de probar el código exacto que di más arriba en x86_64 Linux y me funciona. –

+0

Aquí está mi experiencia exacta: http://pastebin.ca/2133787 - No creo que hice nada diferente a usted. ¿Qué versión de gcc estás usando? Actualicé mi publicación con esa información. –

7

El problema es que (aparte de la declaración de devolución faltante) está reutilizando el parámetro va_list sin restablecerlo. Eso no es bueno.

intentar algo como:

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

(. Y a su vez sobre las advertencias de su compilador)

No creo que la nota se señala que significa lo que usted piensa que lo hace. Lo leí como: si pasa un va_list directamente (como un valor, no como un puntero), lo único que puede hacer en la persona que llama es va_end.Pero si lo pasa como un puntero, podría, por ejemplo, llamar al va_arg en la persona que llama si el destinatario no "consumió" todos los va_list.

Puede intentar con va_copy sin embargo. Algo así como:

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

Gracias. ¿Puedes ver mi publicación revisada? –

+0

Edité mi respuesta, con otra opción. – Mat

+0

Works For Me (tm) en x86_64 con GCC, aunque estoy "consumiendo" la copia primero. Pero realmente sugiero seguir con el restablecimiento de la lista va_ en la persona que llama. – Mat

Cuestiones relacionadas