Продвижение аргументов по умолчанию в вызовах функций C
Настроить
У меня есть несколько вопросов о продвижении аргументов по умолчанию при вызове функции в C. Вот раздел 6.5.2.2 "Вызовы функций" Пункты 6, 7 и 8 из стандарта C99 (pdf) (выделение добавлено и разбито на списки для простоты чтение):
Пункт 6
- Если выражение, обозначающее вызываемую функцию, имеет тип, который не содержит прототип, целочисленные преобразования выполняются для каждого аргумента и аргументов, которые имеют тип
float
повышены доdouble
, Это называется продвижением аргументов по умолчанию.- Если количество аргументов не равно количеству параметров, поведение не определено.
- Если функция определена с типом, который включает в себя прототип, и любой из прототипов заканчивается многоточием (
, ...
) или типы аргументов после продвижения не совместимы с типами параметров, поведение не определено.- Если функция определена с типом, который не содержит прототип, и типы аргументов после продвижения не совместимы с типами параметров после продвижения, поведение не определено, за исключением следующих случаев:
- один повышенный тип является целочисленным типом со знаком, другой повышенный тип является целочисленным типом без знака, и значение представимо в обоих типах;
- оба типа являются указателями на квалифицированные или неквалифицированные версии типа символа или
void
,
Пункт 7
- Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы посредством присваивания, в типы соответствующих параметров, принимая тип каждого параметра за неквалифицированную версию объявленной тип.
- Многоточие в объявлении прототипа функции останавливает преобразование типа аргумента после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.
Пункт 8
- Никакие другие преобразования не выполняются неявно; в частности, количество и типы аргументов не сравниваются с параметрами параметров в определении функции, которое не включает в себя декларатор прототипа функции.
Что я знаю
- По умолчанию аргументы продвижения
char
а такжеshort
вint
/unsigned int
а такжеfloat
вdouble
- Необязательные аргументы для переменных функций (например,
printf
) подлежат продвижению по умолчанию аргумент
Для справки, мое понимание прототипа функции таково:
void func(int a, char b, float c); // Function prototype
void func(int a, char b, float c) { /* ... */ } // Function definition
Вопрос
Мне действительно тяжело все это делать. Вот несколько вопросов, которые у меня есть:
- Действительно ли поведение прототипированных и непрототипированных функций так сильно отличается, например, в отношении продвижения по умолчанию и неявных преобразований?
- Когда происходит продвижение аргумента по умолчанию? Это всегда? Или это только в особых случаях (например, с помощью переменных функций)? Зависит ли это от того, является ли функция прототипом?
3 ответа
Ответ Uprooted AProgrammer - это реальные товары.
Для тех из вас, кто интересуется, почему все так: в темные времена до 1988 года не было такого понятия, как прототип функции в классическом "K&R" C, и продвижение аргументов по умолчанию было начато, потому что (а) по существу "свободный", так как для добавления байта в регистр не требуется больше, чем для добавления слова в регистр, и (b) для сокращения потенциальных ошибок при передаче параметров. Эта вторая причина никогда не усекала его, поэтому введение прототипов функций в ANSI C было самым важным изменением в языке C.
Относительно того, когда запускаются продвижения по умолчанию: продвижения аргументов по умолчанию используются именно тогда, когда ожидаемый тип аргумента неизвестен, то есть, когда нет прототипа или когда аргумент является переменным.
(Non-variadic) параметры для функций с прототипом преобразуются в соответствующий тип, который может быть char, short, float.
Параметры для функций без прототипа и параметров с переменными параметрами подвергаются продвижению по умолчанию.
Если вы определяете функцию с помощью прототипа и используете ее без прототипа или наоборот, и она имеет параметры типа char, short или float, у вас, вероятно, будут проблемы во время выполнения. У вас будут такие же проблемы с переменными функциями, если повышенный тип не соответствует тому, что используется при чтении списка аргументов.
Пример 1: проблема при определении функции с прототипом и использовании ее без.
definition.c
void f(char c)
{
printf("%c", c);
}
use.c
void f();
int main()
{
f('x');
}
может потерпеть неудачу, потому что будет передано int и функция ожидает символ.
Пример 2: проблема при определении функции без прототипа и использовании ее с одним.
definition.c
void f(c)
char c;
{
printf("%c", c);
}
(Это своего рода определение очень старомодно)
use.c
void f(char c);
int main()
{
f('x');
}
может потерпеть неудачу, потому что ожидается int, но будет передан символ.
Примечание: вы заметите, что все функции из стандартной библиотеки имеют типы, которые являются результатом продвижений по умолчанию. Таким образом, они не создавали проблем при переходе, когда были добавлены прототипы.
Ваша путаница проистекает из очень небольшого недопонимания терминологии - как декларации, так и определения могут включать прототипы (или нет):
void func(int a, char b, float c);
Это объявление функции, которое включает в себя прототип.
void func(int a, char b, float c) { /* ... */ }
Это определение функции, которое включает в себя прототип.
"Прототип" и "не прототип" - это просто атрибуты типа функции, и как объявления, так и определения представляют тип функции.
Таким образом, вы можете иметь объявление без прототипа:
void func();
или вы можете иметь определение без прототипа (стиль K&R C):
void func(a, b, c)
int a;
char b;
float c;
{ /* ... */ }