Последний именованный параметр not function или array?
Этот вопрос касается функций vararg и последнего именованного параметра, перед многоточием:
void f(Type paramN, ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
Я читал в стандарте C и нашел следующее ограничение для макроса va_start
:
Параметр parmN является идентификатором самого правого параметра в списке переменных параметров в определении функции (тот, который был непосредственно перед,...). Если параметр parmN объявлен с классом хранения регистров, с типом функции или массива или с типом, который несовместим с типом, который возникает после применения промоакций по умолчанию, поведение не определено.
Интересно, почему поведение undefined для следующего кода
void f(int paramN[], ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
а не undefined для следующих
void f(int *paramN, ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
Макросы предназначены для реализации с помощью чистого кода C. Но чистый C-код не может определить, был ли объявлен paramN
как массив или как указатель. В обоих случаях тип параметра настраивается как указатель. То же самое верно для параметров типа функции.
Интересно: Каково обоснование этого ограничения? У некоторых компиляторов есть проблемы с реализацией этого, когда эти корректировки параметров на месте внутри? (Такое же поведение undefined указано для С++ - так что мой вопрос касается и С++).
Ответы
Ответ 1
Ограничение по параметрам регистра или параметрам функции, вероятно, примерно такое:
- вам не разрешается использовать адрес переменной с классом хранения
register
.
- указатели на функции иногда сильно отличаются от указателей на объекты. Например, они могут быть больше, чем указатели на объекты (вы не можете надежно преобразовать указатель на указатель на объект и обратно), поэтому добавление некоторого фиксированного числа в адрес указателя функции может не привести вас к следующему параметру, Если
va_start()
и/или va_arg()
были реализованы путем добавления некоторой фиксированной суммы к адресу paramN
, а указатели функций были больше, чем указатели объектов, то вычисление закончилось бы с неправильным адресом для объекта va_arg()
. Возможно, это не лучший способ реализовать эти макросы, но могут быть платформы, которые (или даже нуждаются) в этом типе реализации.
Я не могу придумать, в чем проблема состоит в том, чтобы предотвратить разрешение параметров массива, но PJ Plauger говорит об этом в своей книге "Библиотека стандартного C":
Некоторые ограничения, налагаемые на макросы, определенные в <stdarg.h>
, кажутся излишне серьезными. Для некоторых реализаций они есть. Однако каждый из них был представлен для удовлетворения потребностей, по крайней мере, одной серьезной реализации С.
И я думаю, что мало кто знает больше о библиотеках C, чем Plauger. Надеюсь, кто-то может ответить на этот конкретный вопрос на самом деле; Я думаю, что это будет интересная мелочь.
Новая информация:
"Обоснование для международного стандарта - Языки программирования - C" говорит об этом va_start()
:
Аргумент parmN
для va_start
должен был помочь разработчикам, написавшим определение согласованного макроса va_start
полностью на C, даже с использованием компиляторов pre-C89 (например, путем принятия адреса параметра). Ограничения на объявление параметра parmN
следуют из намерения, позволяющего реализовать такой вид реализации, поскольку применение оператора и оператора к имени параметра может не привести к предполагаемому результату, если объявление параметров не соответствовало этим ограничениям.
Не то, что это помогает мне с ограничением по параметрам массива.
Ответ 2
Это не undefined. Имейте в виду, что, когда параметр объявлен как int paramN[]
, фактический тип параметра по-прежнему будет распадаться до int* paramN
немедленно (что видно на С++, например, если вы применяете typeid
к paramN
).
Я должен признать, что я не уверен, что этот бит в спецификации даже для, учитывая, что вы не можете иметь параметры функций или типов массивов в первую очередь (так как они будут разлагаться на указатель).
Ответ 3
Я нашел другую соответствующую цитату, от Dinkumware.
Последний параметр не должен иметь зарегистрируйте класс хранения, и он должен имеют тип, который не изменяется переводчик. Он не может иметь:
* an array type
* a function type
* type float
* any integer type that changes when promoted
* a reference type [C++ only]
Таким образом, очевидно, что проблема заключается именно в том, что параметр передается способом, отличным от того, как он объявлен. Интересно, что они также запрещают float и короткие, хотя они должны поддерживаться стандартом.
В качестве гипотезы может случиться так, что некоторые компиляторы имеют проблемы с правилом sizeof
по таким параметрам. Например. может быть, что для
int f(int x[10])
{
return sizeof(x);
}
некоторый (багги) компилятор вернет 10*sizeof(int)
, тем самым нарушив реализацию va_start
.
Ответ 4
Я могу только догадываться, что ограничение register
заключается в том, чтобы облегчить реализацию библиотеки/компилятора - это устраняет особый случай, о котором они могут беспокоиться.
Но я не имею понятия о ограничении массива/функции. Если бы это было только в стандарте С++, я бы рискнул предположить, что существует какой-то неясный сценарий сопоставления шаблонов, где разница между параметром типа T[]
и типом T*
имеет значение, правильная обработка которого осложнит va_start
и т.д. Но поскольку этот пункт также появляется в стандарте C, очевидно, что объяснение исключено.
Мой вывод: контроль над стандартами. Возможный сценарий: некоторые предварительные стандартные компиляторы C внедрили параметры типа T[]
и T*
по-разному, а представитель этого компилятора в комитете по стандарту C имел вышеуказанные ограничения, добавленные к стандарту; этот компилятор позже стал устаревшим, но никто не чувствовал, что ограничения были достаточно сложными, чтобы обновить стандарт.
Ответ 5
С++ 11 говорит:
[n3290: 13.1/3]:
[..] Объявления параметров, которые отличаются только указатель * по сравнению с массивом [] эквивалентны. То есть массив декларация настроена так, чтобы стать объявлением указателя. [..]
и C99:
[C99: 6.7.5.3/7]:
Объявление параметра как '' массива типа должно быть скорректировано на '' квалифицированный указатель на тип, где квалификаторы типа (если есть) являются теми, которые указаны в [и] тип массива. [..]
И ты сказал:
Но чистый C-код не может определить, был ли объявлен paramN
как массив или как указатель. В обоих случаях тип параметра настраивается как указатель.
Правильно, поэтому нет никакой разницы между двумя фрагментами кода, которые вы нам показали. Оба имеют paramN
, объявленные как указатель; там вообще нет типа массива.
Итак, почему была бы разница между ними, когда дело доходит до UB?
Прохождение, которое вы цитировали...
Параметр parmN является идентификатором самого правого параметра в списке переменных параметров в определении функции (тот, который был непосредственно перед,...). Если параметр parmN объявлен с классом хранения регистров, с типом функции или массива или с типом, который несовместим с типом, который возникает после применения рекламных акций по умолчанию, поведение undefined.
... не применяется ни к чему, как и следовало ожидать.