Последний названный параметр не функция или массив? - PullRequest
16 голосов
/ 30 августа 2009

Этот вопрос о функциях vararg и их последнем названном параметре перед многоточием:

void f(Type paramN, ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

Я читал в стандарте C и обнаружил следующее ограничение для макроса va_start:

Параметр parmN является идентификатором самого правого параметра в списке переменных параметров в определении функции (тот, который находится перед, ...). Если параметр parmN объявлен с классом хранения регистров, с типом функции или массива или с типом, который не совместим с типом, который получается после применения продвижения аргументов по умолчанию, поведение не определено.

Интересно, почему поведение не определено для следующего кода

void f(int paramN[], ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

и не определено для следующего

void f(int *paramN, ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

Макросы предназначены для реализации на чистом C-коде. Но чистый код C не может выяснить, был ли paramN объявлен как массив или как указатель. В обоих случаях тип параметра настраивается на указатель. То же самое верно для параметров типа функции.

Интересно: в чем смысл этого ограничения? Есть ли у некоторых компиляторов проблемы с реализацией этого, когда эти настройки параметров находятся внутри? (То же неопределенное поведение указано для C ++ - так что мой вопрос касается также C ++).

Ответы [ 5 ]

6 голосов
/ 30 августа 2009

Ограничение параметров регистра или параметров функции, вероятно, что-то вроде:

  • вам не разрешено брать адрес переменной с классом хранения register.
  • указатели на функции иногда сильно отличаются от указателей на объекты. Например, они могут быть больше, чем указатели на объекты (вы не можете надежно преобразовать указатель функции в указатель объекта и обратно), поэтому добавление некоторого фиксированного числа к адресу указателя функции может не привести вас к следующему параметру , Если бы va_start() и / или va_arg() были реализованы путем добавления некоторой фиксированной суммы к адресу paramN, а указатели функций были бы больше, чем указатели объектов, вычисление получило бы неправильный адрес для возврата объекта va_arg(). Это может показаться не лучшим способом для реализации этих макросов, но могут быть платформы, которые имеют (или даже нуждаются) в реализации этого типа.

Я не могу придумать, в чем заключается проблема запрета разрешения параметров массива, но П. Дж. Плаугер говорит об этом в своей книге «Стандартная библиотека C»:

Некоторые из ограничений, налагаемых на макросы, определенные в <stdarg.h>, кажутся излишне строгими. Для некоторых реализаций они есть. Однако каждый из них был представлен для удовлетворения потребностей как минимум одной серьезной реализации языка Си.

И я представляю, что есть немного людей, которые знают больше о входах и выходах библиотеки C, чем Plauger. Я надеюсь, что кто-то может ответить на этот конкретный вопрос с реальным примером; Я думаю, что это будет интересная мелочь.

Новая информация:


«Обоснование международного стандарта - Языки программирования - C» говорит об этом va_start():

Аргумент parmN для va_start должен был помочь разработчикам, пишущим определение соответствующего макроса va_start полностью в C, даже с использованием компиляторов до C89 (например, путем взятия адреса параметра). Ограничения на объявление параметра parmN вытекают из намерения разрешить такую ​​реализацию, поскольку применение оператора & к имени параметра может не дать ожидаемого результата, если объявление параметра не соответствует этим ограничениям.

Не то чтобы это помогло мне с ограничением параметров массива.

3 голосов
/ 30 августа 2009

Это не неопределенно. Имейте в виду, что когда параметр объявлен как int paramN[], фактический тип параметра будет по-прежнему немедленно уменьшаться до int* paramN (что видно в C ++, например, если применить typeid к paramN).

Я должен признать, что я не уверен, для чего этот бит в спецификации даже, учитывая, что вы не можете иметь параметры типа функции или массива в первую очередь (так как они будут указателем-затуханием).

2 голосов
/ 30 августа 2009

Я нашел другую релевантную цитату, от Dinkumware .

Последний параметр не должен иметь зарегистрировать класс хранения, и он должен иметь тип, который не изменяется переводчик. Не может иметь:

* an array type
* a function type
* type float
* any integer type that changes when promoted
* a reference type [C++ only]

Так что, очевидно, проблема именно в том, что параметр передается не так, как он объявлен. Интересно, что они также запрещают использовать float и short, хотя они должны поддерживаться стандартом.

Гипотеза может заключаться в том, что у некоторых компиляторов возникают проблемы с корректным выполнением sizeof для таких параметров. Например. может быть, для

int f(int x[10])
{
        return sizeof(x);
}

некоторый (глючный) компилятор вернет 10*sizeof(int), что нарушит реализацию va_start.

1 голос
/ 13 ноября 2011

C ++ 11 говорит:

[n3290: 13.1/3]: [..] Объявления параметров, отличающиеся только указатель * против массива [] эквивалентны. То есть массив объявление корректируется, чтобы стать объявлением указателя. [..]

и C99 тоже:

[C99: 6.7.5.3/7]: Объявление параметра в виде «массива типа» должно быть скорректировано для «квалифицированного указателя на». type ’’, где квалификаторы типа (если таковые имеются) указаны в [и] вывод типа массива. [..]

И вы сказали:

Но чистый код C не может определить, был ли paramN объявлен как массив или как указатель. В обоих случаях тип параметра настраивается на указатель.

Правильно, поэтому нет никакой разницы между двумя фрагментами кода, которые вы нам показали. У обоих paramN объявлен как указатель; там вообще нет типа массива.

Так с какой стати разница между ними, когда дело доходит до UB?

Отрывок, который вы цитировали ...

Параметр parmN является идентификатором самого правого параметра в списке переменных параметров в определении функции (тот, который находится перед, ...). Если параметр parmN объявлен с классом хранения регистров, с функцией или типом массива, или с типом, который не совместим с типом, который получается после применения продвижения аргумента по умолчанию, поведение не определено.

... относится к ни к , как и следовало ожидать.

1 голос
/ 30 августа 2009

Я могу только догадываться, что ограничение register существует для облегчения реализации библиотеки / компилятора - это исключает особый случай, о котором им следует беспокоиться.

Но я понятия не имею об ограничении массива / функции. Если бы это было только в стандарте C ++, я бы рискнул предположить, что есть какой-то неясный сценарий сопоставления шаблонов, в котором разница между параметром типа T[] и типом T* имеет значение, правильная обработка которого усложнит va_start и т. Д. Но поскольку этот пункт также присутствует в стандарте C, очевидно, что такое объяснение исключено.

Мой вывод: недосмотр в стандартах. Возможный сценарий: некоторый предстандартный компилятор C по-разному реализовал параметры типа T[] и T*, и представитель этого компилятора в комитете по стандартам C добавил к стандарту вышеупомянутые ограничения; впоследствии этот компилятор устарел, но никто не чувствовал, что ограничения были достаточно убедительными для обновления стандарта.

...