Почему C не разрешает функцию со списком аргументов переменной длины, например:
void f(...)
{
// do something...
}
Почему C не разрешает функцию со списком аргументов переменной длины, например:
void f(...)
{
// do something...
}
Я думаю, что мотивация требования, чтобы функции varargs имели именованный параметр, связана с единообразием va_start
. Для простоты реализации va_start
принимает имя последнего именованного параметра. При типичном соглашении о вызове varargs и в зависимости от того, в каком направлении хранятся аргументы, va_arg
найдет первый vararg по адресу (¶meter_name) + 1
или (first_vararg_type*)(¶meter_name) - 1
, плюс или минус некоторое заполнение для обеспечения выравнивания.
Я не думаю, что есть какая-то конкретная причина, по которой язык не мог поддерживать функции с переменным числом аргументов без именованных параметров. Должна быть альтернативная форма va_start
для использования в таких функциях, которая должна была бы получать первый vararg непосредственно из указателя стека (или, если быть педантичным, указатель фрейма, который фактически является значением, которое указатель стека имел на вход в функцию, так как код в функции вполне мог переместить sp с момента входа в функцию). В принципе это возможно — любая реализация должна иметь доступ к стеку [*] каким-то образом, на каком-то уровне — но некоторых разработчиков это может раздражать. Как только вы узнаете соглашение о вызовах varargs, вы сможете реализовать макросы va_
без каких-либо других знаний, связанных с реализацией, а для этого потребуется также знать, как напрямую обращаться к аргументам вызова. Я реализовал эти макросы varargs раньше, на уровне эмуляции, и это меня бы раздражало.
Кроме того, не так много практического применения функции varargs без именованных параметров. Для функции varargs нет языковой функции для определения типа и количества переменных аргументов, поэтому вызываемый объект в любом случае должен знать тип первого vararg, чтобы прочитать его. Таким образом, вы также можете сделать его именованным параметром с типом. В printf
и друзьях значение первого параметра сообщает функции, какие типы имеют varargs и сколько их существует.
Я предполагаю, что теоретически вызываемый объект мог бы посмотреть на какой-то глобальный объект, чтобы понять, как читать первый аргумент (и есть ли он вообще), но это довольно неприятно. Я, конечно, не стал бы изо всех сил поддерживать это, и добавление новой версии va_start
с дополнительным бременем реализации выходит за рамки моего пути.
[*] или, если реализация не использует стек, к тому, что он использует вместо этого для передачи аргументов функции.
<varargs.h>
(предшественник и вдохновитель стандартного средства) допускал только переменные аргументы. Обоснование C гласит, что возможность иметь аргументы фиксированного типа перед varargs является нововведением процесса стандартизации. Я также отмечу, что C++ позволяет не иметь аргумента фиксированного типа, и что единственное его использование, которое я видел, было в контекстах метапрограммирования шаблонов.
- person AProgrammer; 04.07.2011
Со списком аргументов переменной длины вы должны объявить тип первого аргумента - это синтаксис языка.
void f(int k, ...)
{
/* do something */
}
будет работать нормально. Затем вам нужно использовать va_list
, va_start
, va_end
и т. д. внутри функции для доступа к отдельным аргументам.
C позволяет использовать аргументы переменной длины, но вам нужно использовать va_list, va_start, va_end и т. д. . для этого. Как вы думаете, как реализованы printf и друзья? Тем не менее, я бы рекомендовал против этого. Обычно вы можете сделать то же самое более аккуратно, используя массив или структуру для параметров.
va_start
нужно имя параметра функции.
- person lhf; 04.07.2011
Поэкспериментировав с этим, я сделал эту замечательную реализацию, которую, я думаю, некоторые люди захотят рассмотреть.
template<typename T>
void print(T first, ...)
{
va_list vl;
va_start(vl, first);
T temp = first;
do
{
cout << temp << endl;
}
while (temp = va_arg(vl, T));
va_end(vl);
}
Это гарантирует, что у вас есть минимум одной переменной, но позволяет вам поместить их все в цикл чистым способом.
Нет внутренней причины, по которой C не может принять void f(...). Могло бы, но «дизайнеры» этой функции C решили этого не делать.
Я предполагаю, что их мотивы заключаются в том, что разрешение void f(...) потребует больше «скрытого» кода (который можно рассматривать как среду выполнения), чем его запрет: чтобы сделать отличимым случай f() от f(arg ) (и другие), C должен предоставить способ подсчета количества заданных аргументов, и для этого требуется больше сгенерированного кода (и, вероятно, новое ключевое слово или специальная переменная, например, "nargs" для получения количества), и C обычно старается быть как можно более минималистичным.
...
не допускает аргументов, то есть: для int printf(const char *format, ...);
оператор
printf("foobar\n");
является действительным.
Если вы не укажете хотя бы 1 параметр (который следует использовать для проверки дополнительных параметров), функция не сможет «узнать», как она была вызвана.
Все эти утверждения будут справедливы
f();
f(1, 2, 3, 4, 5);
f("foobar\n");
f(qsort);