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 = ¶ms;
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 = ¶ms;
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);
...