Как определить конец списка va_arg?

У меня есть функция foo(char *n, ...);, мне нужно получить и использовать все необязательные char параметры. у меня возникла идея использовать

while(va_arg(argPtr, char) != NULL)
{
   ...
}

понять, когда я дойду до конца списка. Итак, будет ли работать, если при вызове функции я сделаю foo(n, 't', 'm', '$', NULL); ?

Будет ли NULL считываться va_arg как символ? Или, может быть, есть более распространенный способ определить конец списка, не добавляя NULL в качестве последнего параметра?


person kekyc    schedule 20.05.2016    source источник
comment
если n является строкой формата, то обычно вы ведете себя как printf и семья, сопоставляя количество спецификаторов формата с количеством параметров. Вы уверены, что передается просто переменное количество char? Если это так, вы можете сопоставить -1 или 0 или что-то еще, но NULL - плохая идея по причинам, изложенным @hvd в его комментариях ниже.   -  person Ryan Haining    schedule 21.05.2016
comment
@kekyc, если вы полагаетесь на отрицательные значения char, вам следует использовать signed char, если вы имеете дело только со значениями в диапазоне [0, 128) (ascii), тогда -1 должно быть хорошо.   -  person Ryan Haining    schedule 21.05.2016
comment
Если вы передаете список переменной длины из char аргументов, почему бы просто не передать строку? (Есть некоторые случаи, когда это не сработает, но это очевидное решение.)   -  person Keith Thompson    schedule 21.05.2016
comment
Если у вас есть переменное количество параметров одного типа, заканчивающихся специальным значением, то это значение должно соответствовать типу аргументов. Если вы передаете значения int, вам нужно выбрать какое-то специальное значение int, например 0 или -1. Если вы передаете строки, т. е. char *, вам нужно завершить их с помощью (char *) NULL, как, например, требует execl().   -  person Tom Karzes    schedule 21.05.2016
comment
Хорошо, спасибо, ребята, за потрясающие, полные ответы!   -  person kekyc    schedule 21.05.2016


Ответы (2)


Не существует прямого способа для функции, которая использует va_arg для определения количества или типа(ов) аргументов, переданных данным вызовом.

Предлагаемый вами метод, в частности:

while(va_arg(argPtr, char) != NULL)

это неверно. va_arg(argPtr, char) дает значение типа char, а NULL является константой нулевого указателя. (NULL обычно определяется как 0, что эквивалентно нулевому символу '\0', но на это нельзя полагаться.)

Любая функция с переменным числом аргументов должна иметь возможность для вызывающей стороны указать количество и типы аргументов. Например, функции *printf делают это через (невариативную) строку формата. Функции POSIX execl*() ожидают последовательность из char* аргументов; конец списка аргументов помечен вызывающим кодом (char*)NULL. Возможны и другие методы, но почти все они зависят от информации, предоставляемой во время выполнения в аргументах. (Вы можете использовать другой метод, например, глобальную переменную. Пожалуйста, не делайте этого.)

Это возлагает на вызывающего бремя обеспечения согласованности аргументов, передаваемых функции. Сама функция не имеет возможности подтвердить это. Неправильные вызовы, такие как printf("%d\n", "hello") или execlp("name", "arg1"), имеют неопределенное поведение.

Еще одно: вы не можете использовать va_arg с аргументом типа char. Когда вы вызываете функцию с переменным числом аргументов, аргументы, соответствующие , ..., расширяются. Целочисленные аргументы типов более узких, чем int, повышаются до int или unsigned int, а аргументы типа float повышаются до double. Если вызывающий объект передает аргумент типа char, функция должна вызывать va_arg(argPtr, int).

(В очень неясных обстоятельствах, с которыми вы вряд ли столкнетесь, char можно повысить до unsigned int. Это может произойти только в том случае, если простое char не имеет знака и sizeof (int) == 1, что подразумевает, что байт состоит как минимум из 16 бит.)

person Keith Thompson    schedule 20.05.2016
comment
Очень красиво и полно. Непонятные методы включают в себя глобальную переменную или что-то среди аргументов (не обязательно конечный сигнал). Тем не менее, два способа, упомянутые в этом прекрасном ответе, являются наиболее распространенными. - person chux - Reinstate Monica; 21.05.2016
comment
@chux: Хороший вопрос о глобальных переменных. Я добавил соответствующие ласковые слова. - person Keith Thompson; 21.05.2016
comment
Я вспоминаю эти слабые методы кодирования из тех дней, когда foo(...) был разрешен - до C89. - person chux - Reinstate Monica; 21.05.2016
comment
@chux: Интересно. Я не знал, что функции с переменным числом аргументов до ANSI (предположительно использующие <varargs.h> для получения своих аргументов) использовали синтаксис .... - person Keith Thompson; 21.05.2016
comment
Даже некоторые компиляторы C99 допускали ноль аргументов в качестве расширения. Если синтаксис был ... или нет, я, возможно, неправильно запомнил, но дело в том, что нулевые аргументы были закодированы в какой-то момент в переданном как расширение и/или предварительно -С89. Это редко было полезно. - person chux - Reinstate Monica; 21.05.2016
comment
Разве NULL не интерпретируется явно как ((void* )0x0). Присвоение его целочисленному типу должно привести к предупреждению. - person Edenia; 31.03.2019
comment
@Edenia: в C NULL расширяется до определяемой реализацией константы нулевого указателя. Константа нулевого указателя определяется как целочисленное константное выражение со значением 0 или такое выражение, приведенное к typevoid *. Таким образом, NULL может расширяться до любого из ((void*)0), или 0, или ('-'-'-'), если разработчик находится в извращенном настроении. Будет ли int n = NULL; производить диагностику, зависит от реализации. (Обратите внимание, что в C++ действуют другие правила.) - person Keith Thompson; 31.03.2019

Основная идея сработает. Но вы заполнили детали таким образом, что почти наверняка этого не произойдет.

Обычные правила неявного преобразования не применяются, когда вы используете переменные аргументы. Вместо этого имеет место повышение аргумента по умолчанию, что означает, что любой целочисленный тип, меньший int/unsigned int, преобразуется в одно из этих - это не единственное повышение, но единственное подходящее здесь - и которое также означает, что автоматическое преобразование в любой тип, указанный вами с помощью va_arg, не выполняется.

Это означает, что:

  • Вы никогда не сможете использовать va_arg(..., char), так как невозможно передать char в качестве аргумента функции с переменным числом переменных.
  • Вы никогда не сможете передать NULL в качестве аргумента функции с переменным числом аргументов, поскольку его конкретный тип сильно зависит от реализации. Реалистичные типы — это int, long, void *, и есть множество других менее реалистичных, но не менее достоверных типов.

Вы можете изменить

while(va_arg(argPtr, char) != NULL)

to

while(va_arg(argPtr, int) != 0)

и звонок

foo(n, 't', 'm', '$', NULL);

to

foo(n, 't', 'm', '$', 0);
person Community    schedule 20.05.2016