Аргумент va_list на самом деле не является списком va_list - PullRequest
8 голосов
/ 04 февраля 2020

При попытке скомпилировать этот код

#include <stdarg.h>

void bar_ptr(int n, va_list *pvl) {
    // do va_arg stuff here
}

void bar(int n, va_list vl) {
    va_list *pvl = &vl; // error here
    bar_ptr(n, pvl);
}

void foo(int n, ...) {
    va_list vl;
    va_list *pvl = &vl; // fine here
    va_start(vl, n);
    bar(n, vl);
    va_end(vl);
}

int main() {
    foo(3, 1, 2, 3);
    return 0;
}

компилятор G CC выводит предупреждение о initialization from incompatible pointer type в функции bar. То же самое утверждение прекрасно в foo.

. Кажется, что тип агента типа va_list не является va_list. Это легко проверить с помощью утверждения c типа

_Static_assert(sizeof(vl) == sizeof(va_list), "invalid type");

в функции bar. С G CC _Static_assert завершается неудачно. То же самое можно проверить и в C ++ с declytpe и std::is_same.

Я хотел бы взять адрес аргумента va_list vl bar и передать его в качестве аргумента bar_ptr , do думает так, как описано в этой теме . С другой стороны, можно набрать bar_ptr(n, pvl) напрямую из main, заменив bar(n, vl).

В соответствии со сноской 253 C11 окончательного варианта ,

Разрешено создавать указатель на va_list и передавать этот указатель другой функции

Почему этого нельзя сделать, если va_list определен в качестве аргумента функции а не в теле функции?

Обходной путь:

Даже если это не отвечает на вопрос, возможный обходной путь - изменить содержимое bar на используя локальную копию аргумента, созданного с помощью va_copy:

void bar(int n, va_list vl) {
    va_list vl_copy;
    va_copy(vl_copy, vl);
    va_list *pvl = &vl_copy; // now fine here
    bar_ptr(n, pvl);
    va_end(va_copy);
}

Ответы [ 2 ]

4 голосов
/ 04 февраля 2020

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

Странное правило ( 7.16p3 ) относительно того, как va_list Передается в основном допускает возможность того, что va_list может иметь тип массива или обычного типа.

Я лично обертываю va_list в struct, поэтому мне не нужно иметь дело с этим.

Когда вы передаете указатели на такой struct va_list_wrapper, это в основном так, как если бы вы передали указатели на va_list, и тогда применяется сноска 253 , которая дает вам разрешение на вызываемый и вызывающий манипулируют одним и тем же va_list с помощью такого указателя.

(То же самое относится к jmp_buf и sigjmp_buf с setjmp.h. Как правило, этот тип массива для корректировки указателя имеет вид одна из причин, по которой лучше избегать типизированных массивов typedef. Это просто создает путаницу, ИМО.)

2 голосов
/ 04 февраля 2020

Другое решение (только C11 +):

_Generic(vl, va_list: &vl, default: (va_list *)vl)

Объяснение: если vl имеет тип va_list, то va_list не является типом массива, и просто получить адрес можно получить va_list * указывает на это. В противном случае он должен иметь тип массива, и тогда вам разрешается приводить указатель на первый элемент массива (независимо от того, какой это тип) на указатель на массив.

...