Подтвердить что ты не робот

Последний именованный параметр 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 указано для С++ - так что мой вопрос касается и С++).

4b9b3361

Ответ 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.

... не применяется ни к чему, как и следовало ожидать.