Продвижение аргументов по умолчанию в вызовах функций C

Настройка

У меня есть несколько вопросов о рекламных акциях по умолчанию при вызове функции в C. Здесь раздел 6.5.2.2 "Вызов функций" Пункты 6, 7 и 8 из стандарт C99 (pdf) (выделение и разбивка на списки для удобства чтения):

Пункт 6

  • Если выражение, обозначающее вызываемую функцию, имеет тип, в котором не содержит прототип, целые акции выполняются для каждого аргумента, а аргументы с типом float - до double. Они называются рекламными акциями по умолчанию.
  • Если количество аргументов не равно числу параметров, поведение undefined.
  • Если функция определена с типом, в котором содержит прототип, и либо прототип заканчивается эллипсисом (, ...), либо типы аргументов после продвижения по службе несовместимы с типами параметров, поведение undefined.
  • Если функция определена с типом, в котором не содержит прототип, а типы аргументов после продвижения по службе не совместимы с типами параметров после продвижения по службе, поведение undefined, за исключением следующих случаев:
    • один продвинутый тип - это целочисленный тип со знаком, другой продвинутый тип - это соответствующий целочисленный тип без знака, и значение представляется в обоих типах;
    • Оба типа являются указателями на квалифицированные или неквалифицированные версии типа символа или 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

Вопрос

У меня очень тяжелое время. Вот несколько вопросов, которые у меня есть:

  • Поведение прототипированных и не прототипированных функций действительно сильно отличается, например, в отношении поощрений по умолчанию и неявных преобразований?
  • Когда появляются рекламные акции по умолчанию? Это всегда? Или это только в особых случаях (например, с вариационными функциями)? Это зависит от того, прототипирована ли функция?

Ответ 1

Упрощенный AProgrammer ответ - это настоящие товары.

Для тех из вас, кто задается вопросом, почему все так: в темные века до 1988 года не было такого понятия, как прототип функции в классическом "K & R" C, и были объявлены промо-акции по умолчанию, потому что ( a) были, по существу, "свободны", так как он больше не должен ставить байт в регистр, чем помещать слово в регистр, и (b) сократить возможные ошибки при передаче параметров. Эта вторая причина никогда не сокращала его, поэтому введение прототипов функций в ANSI C было единственным самым важным изменением на языке C.

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

Ответ 2

  • Параметры
  • (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 будет передан, а функция ожидает a char.

Пример 2: проблема при определении функции без прототипа и ее использовании с одним.

definition.c

void f(c)
   char c;
{
   printf("%c", c);
}

(Это своеобразное определение очень старомодно)

use.c

void f(char c);

int main()
{
   f('x');
}

может выйти из строя, потому что ожидается int, но будет передан char.

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

Ответ 3

Ваше замешательство проистекает из очень незначительного непонимания терминологии - как декларации, так и определения могут включать прототипы (или нет):

void func(int a, char b, float c);

Это объявление функции, которое включает прототип.

void func(int a, char b, float c) { /* ... */ }

Это определение функции, которое включает прототип.

"Prototyped" и "non-prototyped" - это просто атрибуты типа функции, и как декларации, так и определения вводят тип функции.

Итак, вы можете иметь декларацию без прототипа:

void func();

или вы можете иметь определение без прототипа (стиль K & R C):

void func(a, b, c)
    int a;
    char b;
    float c;
{ /* ... */ }