Могу ли я использовать `va_list` дважды следующим образом: - PullRequest
1 голос
/ 21 марта 2019

Могу ли я использовать va_list следующим образом:

void myself_printf(char* key, char* value, ...) {
    char real_key[1024];
    char real_value[1024];
    va_list args;
    va_start(args, value);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args);

    va_end(args);
}

используется в демо

myself_printf("%d-%s", "%s-%d", 12, "key", "value", 24);
expect: real_key is "12-key", and real_value is "value-24"

Ответы [ 2 ]

5 голосов
/ 21 марта 2019

Строго говоря, вы должны использовать va_copy, поскольку vsnprintf может сделать недействительным args.

void myself_printf(char* key, char* value, ...) {
    char real_key[1024000];
    char real_value[1024000];
    va_list args, args2;
    va_start(args, value);
    va_copy(args2, args);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args2);

    va_end(args);
    va_end(args2);
}

Это то, для чего va_copy предназначен.

Как уже отмечалось, это большой объем стекового пространства, хотя он находится в пределах типичных размеров стека. Попробуйте использовать vasprintf, если оно доступно.

Цитирование

n1548 §7.16

Объект ap может быть передан в качестве аргумента другой функции; если эта функция вызывает макрос va_arg с параметром ap, значение ap в вызывающей функции является неопределенным…

Другими словами, вы не можете использовать args после того, как передадите его vsnprintf.

Это поясняется в сноске 281:

Поскольку функции vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf и vsscanf вызывают макрос va_arg, значение arg после возврата является неопределенным.

Хотя кажется, что va_list передается по значению, это не означает, что оно фактически передается по значению или что va_list сам инкапсулирует все свое состояние. Обычный трюк с typedefs в C состоит в том, чтобы объявить их как массив из 1 элемента:

typedef int my_type[1];

Так как my_type затухает до типа указателя, когда он передается функцией, только представляется для передачи по значению.

Демо

#include <stdio.h>
#include <stdarg.h>
void func(const char *msg, ...) {
    va_list ap;
    va_start(ap, msg);
    vfprintf(stdout, msg, ap);
    vfprintf(stdout, msg, ap);
    va_end(ap);
}
int main(int argc, char **argv) {
    func("%d + %d = %d\n", 2, 3, 5);
    return 0;
}

На моем компьютере вывод:

2 + 3 = 5
590862432 + -1635853408 = 1586038440
3 голосов
/ 21 марта 2019

В качестве альтернативы использованию va_copy() - в соответствии с предложением Дитрих Эпп в ответе - вы можете просто использовать va_start() и va_end() дважды.

void myself_printf(char *key_fmt, char *value_fmt, ...)
{
    char real_key[1024];
    char real_value[1024];
    va_list args;

    va_start(args, value);
    vsnprintf(real_key, sizeof(real_key), key_fmt, args);
    va_end(args);

    vs_start(args, value);
    vsnprintf(real_value, sizeof(real_key), value_fmt, args);
    va_end(args);

    …do something useful with real_key and real_value…
}

Исходная версия вопроса использовалась char real_key[1024000]; и аналогично для real_value. Выделение почти 2 МБ данных в стеке не будет надежно работать в Windows (ограничение обычно составляет 1 МБ стека) и занимает очень много места в системах Unix (где размер стека обычно составляет 8 МБ). Будьте осторожны!

Вам нужно будет использовать va_copy(), если va_list был передан в качестве аргумента вашей функции. Например:

void myself_printf(char* key, char* value, ...)
{
    va_list args;    
    va_start(args, value);
    myself_vprintf(key, value, args);
    va_end(args);
}

void myself_vprintf(char *key_fmt, char *value_fmt, va_list args1)
{
    char real_key[1024];
    char real_value[1024];
    va_list args2;
    va_copy(args2, args1);

    vsnprintf(real_key, sizeof(real_key), key_fmt, args1);
    vsnprintf(real_value, sizeof(real_value), value_fmt, args2);
    va_end(args2);

    …do something useful with real_key and real_value…
}

Спецификация va_end() гласит:

Макрос va_end облегчает нормальный возврат из функции, чей список переменных-переменных был упомянут расширением макроса va_start, или функции, содержащей расширение макроса va_copy, которая инициализировала va_list ap. Макрос va_end может изменить ap, чтобы он больше не мог использоваться (без повторной инициализации макросом va_start или va_copy). Если нет соответствующего вызова макроса va_start или va_copy, или если макрос va_end не вызывается до возврата, поведение не определено.

Обратите внимание, что спецификация функции vfprintf() включает сноску 288 , которая гласит:

Поскольку функции vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf и vsscanf вызывают макрос va_arg, значение arg после возврата является неопределенным.

Конечно, сноски не являются нормативными, но это явный признак того, что функции, как ожидается, будут использовать va_arg, и, следовательно, двойное использование arg, как показано в вопросе, приводит к неопределенному поведению, если нет промежуточного вызова на va_end и другое va_start или использование va_copy (и его соответствие va_end).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...