va_list неправильное поведение в Linux - PullRequest
8 голосов
/ 30 марта 2012

У меня есть код, который преобразует переменные параметры в va_list, а затем передает список в функцию, которая затем вызывает vsnprintf. Это прекрасно работает в Windows и OS X, но в Linux это приводит к странным результатам.

В следующем примере кода:

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

Выход второго vsnprintf вызова равен 17, а результат strcmp равен 31; но я не понимаю, почему vsnprintf вернул бы 17, поскольку This is a test. - 15 символов, добавьте NULL, и вы получите 16.

Связанные темы, которые я видел, но не затрагиваю тему:


С ответом @ Mat (я повторно использую объект va_list, который не разрешен), это прямо касается первой связанной нити, с которой я связался. Поэтому я попытался использовать этот код:

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

Что, согласно спецификации C99 (сноска в Разделе 7.15), должно работать:

Разрешено создавать указатель на va_list и передавать этот указатель другой функции, в этом случае оригинальная функция может сделать дальнейшее использование исходного списка после возврата другой функции.

Но мой компилятор (gcc 4.4.5 в режиме C99) выдает мне эту ошибку относительно первой строки myPrintfInner:

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

И полученный двоичный файл производит тот же эффект, что и в первый раз.


Нашел это: Неправильно ли обрабатывает GCC указатель на va_list, переданный в функцию?

Предлагаемый обходной путь (который не гарантированно сработал, но работал на практике) - сначала использовать arg_copy:

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

Ответы [ 2 ]

12 голосов
/ 30 марта 2012

Как отмечает Мэт, проблема в том, что вы повторно используете va_list. Если вы не хотите реструктурировать свой код, как он предлагает, вы можете использовать макрос C99 va_copy(), например:

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

На компиляторах, которые не поддерживают C99, вы можете использовать __va_copy() вместо этого или определить свою собственную va_copy() реализацию (которая будет непереносимой, но вы всегда можете использовать компилятор / Платформа нюхает в заголовочном файле, если вам действительно нужно). Но на самом деле прошло 13 лет & mdash; любой приличный компилятор должен поддерживать C99 в наши дни, по крайней мере, если вы предоставите ему правильные параметры (-std=c99 для GCC).

7 голосов
/ 30 марта 2012

Проблема в том, что (кроме отсутствующего оператора return) вы повторно используете параметр va_list без его сброса.Это нехорошо.

Попробуйте что-то вроде:

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

(и включите предупреждения вашего компилятора.)

Не думаю, что сноска, на которую вы указываете, означает, чтоВы думаете, что это так.Я читаю это так: если вы передаете va_list напрямую (как значение, а не указатель), единственное, что вы можете сделать в вызывающей программе, это va_end это.Но если вы передадите его в качестве указателя, вы можете, скажем, позвонить va_arg в вызывающем абоненте, если вызываемый не "потребляет" все va_list.

Вы можете попробовать с помощью va_copy,Что-то вроде:

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