Что означает этот оператор в стандарте C11 (о вариативных функциях)?

При цене 6.5.2.2.6 $ стандарт C11:

Если выражение, обозначающее вызываемую функцию, имеет тип, не включающий прототип, целочисленные повышения выполняются для каждого аргумента, а аргументы с типом float повышаются до удвоения. Это называется продвижением аргументов по умолчанию. Если количество аргументов не равно количеству параметров, поведение не определено. Если функция определена с типом, который включает прототип, и либо прототип заканчивается многоточием (, ...), либо типы аргументов после продвижения несовместимы с типами параметров, поведение не определено. Если функция определена с типом, который не включает прототип, и типы аргументов после продвижения несовместимы с типами параметров после продвижения, поведение не определено, за исключением следующих случаи: ...

Что это значит - я толком не понимаю (особенно первую часть). Однако из того, что я могу сказать, это означает, что определение такой функции:

void func(int a, int b, ...)
{
}

А затем называть это неопределенным поведением, которое я считаю глупым.


person AnArrayOfFunctions    schedule 03.01.2016    source источник


Ответы (2)


Ситуация следующая: вы можете объявить функцию без списка параметров и вызвать эту функцию:

int main(void)
{ 
    extern void f();   // no parameter list!
    char c = 'x';
    f(c, 1UL, 3.5f);
}

В этой ситуации аргументы продвигаются по умолчанию: первый аргумент повышается до int или unsigned int (в зависимости от платформы), второй остается unsigned long, а третий повышается до double.

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

void f(int, unsigned long, double)
{
    // ...
}

В приведенном вами стандарте говорится, что поведение не определено, если типы параметров в этом определении несовместимы с повышенными типами вызова или если список параметров заканчивается многоточием.

Как следствие, следует, что если вы хотите использовать функцию с переменными аргументами (используя возможности <stdarg.h> для доступа к аргументам), вы должны объявить функцию с прототипом:

extern void f(int, ...);   // prototype (containing ellipsis)
f(c, 1UL, 3.5f);

Теперь c преобразуется в int, потому что первый параметр типизирован, а второй и третий аргументы продвигаются по умолчанию, как и раньше, потому что они передаются как часть многоточия. В определении f теперь должно использоваться то же объявление. Если хотите, передача аргументов способом, доступным для <stdarg.h> средств, может потребовать от компилятора предварительных знаний, поэтому вы должны предоставить список параметров перед вызовом.

person Kerrek SB    schedule 03.01.2016

Формулировка немного сбивает с толку. Весь абзац говорит о случае, когда прототип не был объявлен для функции во время ее вызова, поэтому выделенный вами раздел предназначен для случая, когда прототип не объявляется при вызове функции, но прототип используется, когда функция определена. Вот пример:

int main(int argc,char** argv)
{
    f(3.0f); /* undefined behavior */
    g(3.0);  /* undefined behavior */
}

int f(float v)
{
    return v;
}

int g(double v,...)
{
    return v;
}

В этом примере, когда вызывается f, прототип не был объявлен, поэтому 3.0f повышается до double. Однако функция позже определяется с помощью прототипа, который принимает float вместо double, поэтому поведение не определено.

Аналогично для g поведение не определено, потому что в прототипе определения используются числа.

person Vaughn Cato    schedule 03.01.2016
comment
Этот стандарт C странный. Где многоточие во всем том, что вы мне показываете? Потому что они существуют в выделенном мною стандартном заявлении. - person AnArrayOfFunctions; 03.01.2016
comment
@FISOCPP В выделенном вами предложении упоминаются два случая, один из которых включает ... параметры. Пример Вона Катона относится к другому случаю. - person fuz; 03.01.2016
comment
@FISOCPP: Вы выделили раздел для двух разных случаев. Я расширил свой ответ, включив в него также случай elipses. - person Vaughn Cato; 03.01.2016
comment
Что также означает, что мы можем создавать экземпляры функций, не определяя и не объявляя их раньше? - person AnArrayOfFunctions; 03.01.2016
comment
@FISOCPP: Это правда, но это отдельная тема. Когда вы вызываете функцию, которая не была объявлена, она неявно объявляется как функция, возвращающая int без прототипа. В качестве альтернативы вы можете объявить функцию, но не иметь прототипа (пустые скобки) `. - person Vaughn Cato; 03.01.2016