Продвижение аргументов и параметров по умолчанию в C

Я изучал продвижение аргументов по умолчанию и застрял в одном месте. В C 2011 (ISO / IEC 9899: 2011) соответствующая часть выглядит следующим образом:

§6.5.2.2 Вызов функций

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

- один продвинутый тип - это целочисленный тип со знаком, другой продвинутый тип - соответствующий беззнаковый целочисленный тип, и значение может быть представлено в обоих типах;

- оба типа являются указателями на квалифицированные или неквалифицированные версии символьного типа или void.

В последних трех строках абзаца говорится о типе функции, которая не включает прототип при его определении.

В нем говорится, что если типы аргументов после продвижения несовместимы с типами параметров после продвижения, поведение не определено.

Теперь у меня есть очень глупые сомнения, что если и объявление функции, и определение функции не включают прототип, упомянутый в этом абзаце, то о том, о каких параметрах они говорят в последних трех строках абзаца. И что означают здесь «параметры после продвижения», поскольку я изучал только продвижение аргументов. Что такое «параметр Promotions»?

Вы также можете привести примеры исключительных случаев, упомянутых в последнем. Если бы кто-то мог объяснить это на подходящем примере, это было бы действительно заметно.


person LocalHost    schedule 16.05.2020    source источник


Ответы (1)


До того, как C был стандартизирован (он же до C89), функции были определены по-другому. Этот стиль по-прежнему поддерживается в C11 для обеспечения обратной совместимости. Не используйте его, если вся цель - развлечься:

int add_ints(); //forward-declaration has no parameters

add_ints(a, b)
//implicit type for return and parameters is int, this only works in pre-standard C or C89/C90
//int a, b; //remove this comment in C99/C11 for it to compile (also add return type int)
{
    return a + b; //side note: old K&R compilers required parantheses around the return expression
}

В некотором смысле эти функции имеют параметры, которые ведут себя как varargs. Вызывающий не знает, какие параметры ожидает функция (как и в случае с varargs). Он может передавать ему любые параметры и любое их количество. Однако это, конечно, неопределенное поведение, если количество параметров в операторе вызова не совпадает с количеством параметров в объявлении.

Конечно, здесь возникает проблема. Если вызывающий хочет передать short, как он узнает, ожидает ли функция short (и передает его напрямую) или int (и нуждается в его преобразовании)? Не может, поэтому была достигнута точка соприкосновения. Было решено, что:

  • char и short получают повышение до int
  • float получает повышение до double

Это происходит для всех функций, определенных таким образом (стиль K&R), и для параметров varargs. Таким образом, функция K&R никогда не будет ожидать параметра short, поэтому компилятор всегда будет продвигать параметры short к int.

Конечно, как сказал @aschepler, вы все равно можете определить такую ​​функцию, как:

short add_shorts(a, b)
    short a, b;
{
    return a + b;
}

Это означает, что параметры сначала преобразуются в int и передаются функции, и только затем функция преобразует их в short и добавляет их.

Будьте осторожны с такими функциями, как printf():

printf("%.f", 3); //passes an int: UB and also wrong answer (my compiler prints 0)
printf("%.f", 3.0); //correct
printf("%.f", (double)3); //correct

На самом деле вы можете довольно часто видеть функции K&R, особенно если автор не обратил внимание на добавление ключевого слова void в функцию, которая не принимает параметров:

int f1() //K&R function
{
    return 0;
}
int f2(void) //Standard function
{
    return 0;
}

int main(void) //Don't forget void here as well :P
{
    int a = f1(); //Returns 0
    int b = f2(); //Returns 0
    int c = f1(100); //UB - invalid number of parameters, in practice just returns 0 :)
    int d = f2(100); //Compiler error - parameter number/types don't match

    //A good compiler would give a warning for call #3, but mine doesn't :(
}

РЕДАКТИРОВАТЬ: Не знаю, почему, но cppreference классифицирует функции, определенные как f1 (), как их собственный тип функции (без параметров без void) вместо функций K&R. У меня нет стандарта передо мной, но даже если стандарт говорит то же самое, они должны вести себя так же, и у них есть история, о которой я упоминал.

Продвижение аргументов по умолчанию

Объявления функций в C

person DarkAtom    schedule 16.05.2020
comment
И тогда вы также можете иметь short add_shorts(a, b) short a, b; { return a+b; }, где вызывающий будет по-прежнему предоставлять возможно повышенные int значения, но затем определение функции решает преобразовать их при вызове. - person aschepler; 16.05.2020
comment
Я понял все, что вы объяснили, но я все еще сомневаюсь. О каких параметрах они говорят в последних 3 строках абзаца, поскольку и объявление функции, и определение не имеют прототипа. А что такое параметр раскрутки ?? Я понял, что аргументы продвигаются, но что это за параметр продвижение ?? - person LocalHost; 16.05.2020
comment
Примечание: cppreference не является источником мудрости или истины. - person wildplasser; 16.05.2020
comment
Если функция не имеет прототипа, это означает, что это функция в стиле K&R. Думаю, вы думали, что это функция без параметров ??? - person DarkAtom; 16.05.2020
comment
@DarkAtom Нет, без прототипа все (и возвращаемое значение, и аргумент (ы)) предполагается равным int. - person wildplasser; 16.05.2020
comment
@wildplasser Кроме стандарта (который стоит денег), никакой другой источник не заслуживает доверия. Я выбрал cppreference, потому что его легко просматривать. Если у вас есть лучший источник, пожалуйста, дайте ссылку на него. Однако не связывайтесь с черновиком стандарта, вы можете сказать из вопроса, что OP уже имеет черновик / фактический стандарт. - person DarkAtom; 16.05.2020
comment
@wildplasser Это неправда. Посмотрите на функцию @aschepler. Если вы замените short на double или любой другой тип указателя, аргументы НЕ преобразуются в _3 _ !!! Как я сказал в своем ответе, только меньшие целочисленные типы передаются как int. - person DarkAtom; 16.05.2020
comment
Там нет прототипа (или: неправильного) в масштабе. Кроме того, пример неполный. (и не имеет отношения к делу). Это происходит случайно, вызвано акциями. - person wildplasser; 16.05.2020
comment
@wildplasser Я думаю, вы тоже не понимаете, что означает функция без прототипа. double f(double a) {return 2.0;} имеет прототип, а double f(a) double a; {return 2.0;} нет. Любая стандартная функция имеет прототип, а любая функция в стиле K&R - нет. Я не знаю, что вы подразумеваете под охватом прототипа. - person DarkAtom; 16.05.2020
comment
@DarkAtom Да, именно так я и думал. а что с параметром акции ??? - person LocalHost; 16.05.2020
comment
@Noshiii При быстром поиске в Google выскакивает продвижение аргументов по умолчанию. Думаю, это разные термины для одного и того же :) Я только что поискал в гугле, так что могу ошибаться - person DarkAtom; 16.05.2020
comment
@DarkAtom Хорошо, если это так, как мы это объясним: типы аргументов после продвижения несовместимы с типами параметров после продвижения, поведение не определено :( - person LocalHost; 16.05.2020
comment
Параметры те же, что в прототипе (или K&R-псевдопрототипе). Аргументы - это те самые аргументы, которые были приняты. Обновил свой ответ. Посмотрите на пример printf(). Я передаю int, но функция ожидает double. Целые числа не двоично совместимы с числами с плавающей запятой, поэтому UB возникает, потому что преобразование аргументов по умолчанию не преобразует int в double, как мы хотим, оно оставляет его в покое. В примере с add_shorts аргумент Promotions передает int, который двоично совместим с short (что ожидает функция), поэтому UB там нет. - person DarkAtom; 16.05.2020
comment
охххх, я наконец понял, что они хотели сказать. Это означает, что последние три строки предназначены для определения функции в стиле K&R. Верно? (Когда функция def меньше прототипа). - person LocalHost; 16.05.2020
comment
Да, функции стиля K&R, но также параметры varargs. По сути, в стандарте очень причудливый способ сказать, что UB передает то, чего функция не ожидала (например, передача int вместо double). Это UB, потому что компилятор не может знать, что ожидает функция (такая информация не включается ни в функции K&R, ни в многоточие varargs). - person DarkAtom; 16.05.2020
comment
@DarkAtom: Еще больше усложняет ситуацию тот факт, что в стандарте не предпринимались попытки рассмотреть такие вещи, как необходимость в обычных реализациях поддерживать использование спецификатора fprintf %X для вывода значений типа unsigned char или unsigned short без предварительного явного преобразования в unsigned. Хотя предположительно могут существовать платформы, на которых было бы непрактично поддерживать такое использование, требование, чтобы программы, которые никогда не будут нацелены на такие платформы, использовали спецификаторы формата %hhX или %hX, тратят память и время, не служа никакой полезной цели. - person supercat; 20.05.2020
comment
@supercat Я никогда не понимал смысла тех спецификаторов формата h для printf, кроме как согласовываться с scanf(). Если у вас есть short или unsigned char или любой другой маленький тип int, вы можете передать его %d, и он будет работать с четко определенным поведением (потому что все они подходят для int). - person DarkAtom; 20.05.2020
comment
@DarkAtom: некоторые реализации будут обрабатывать %02hhX таким образом, чтобы вывести не более двух шестнадцатеричных цифр, хотя я не уверен, требует ли Стандарт, чтобы это поведение считалось определенным в обстоятельствах, которые в противном случае привели бы к выводу большего количества (например, вывод отрицательного значение знакового символьного типа). Я бы предположил, что спецификатор hh включен, потому что по крайней мере некоторые реализации будут поддерживать его использование для принудительного усечения значений, но при условии, что все реализации поддерживают его, будут тратить пространство кода при обработке программ, которые никогда его не используют. - person supercat; 20.05.2020
comment
cppreference идет в ногу с последним черновиком стандарта, где функции, определенные как f1 (), являются самостоятельными, поскольку функции K&R были удалены. - person Cubbi; 28.05.2020
comment
@Cubbi cppreference отмечает последние изменения черновика как C2x. Все функции, которые были удалены, помечены как таковые и сохраняются на веб-сайте для устаревших целей (насколько мне известно), например, функция gets. - person DarkAtom; 28.05.2020
comment
Правильно, старые безпараметрические функции K&R описаны там как noptr-declarator ( identifier-list(optional) ) (2) (until C2x), новые безпараметрические функции без параметров K&R - это noptr-declarator ( ) (3) (since C2x). Разве вы не говорите о какой-то другой части страницы? - person Cubbi; 28.05.2020
comment
@Cubbi Woah! Хороший улов, упустил! Но ... если они уберут поддержку функций K&R в C2x, почему бы не убрать и поддержку empty ()? Или, что еще лучше, сделать его эквивалентом (недействительным)? В любом случае UB передает ему какие-либо параметры, так почему бы не превратить UB в ошибку компилятора? - person DarkAtom; 28.05.2020
comment
они сделали их эквивалентными (void), как говорит cppreference, эквивалентными объявлению функции со списком параметров, состоящим из единственного ключевого слова void. В текущем C2x open-std.org/jtc1/ sc22 / wg14 / www / docs / n2479.pdf это в 6.7.6.3 Деклараторы функций pp13 - person Cubbi; 28.05.2020