va_list неправильное поведение в Linux

У меня есть код, который преобразует переменные параметры в 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 ответа

Решение

Как отмечает Мэт, проблема в том, что вы повторно используете 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 лет - любой приличный компилятор должен поддерживать C99 в наши дни, по крайней мере, если вы предоставите ему правильные опции (-std=c99 для GCC).

Проблема в том, что (кроме отсутствующего оператора 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);
    ...
Другие вопросы по тегам