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

Настроить

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

Пункт 6

  1. Если выражение, обозначающее вызываемую функцию, имеет тип, который не содержит прототип , то для каждого аргумента выполняются целые рекламные акции, а аргументы с типом float повышаются до double . Они называются рекламными акциями по умолчанию .
  2. Если количество аргументов не равно числу параметров, поведение не определено.
  3. Если функция определена с типом, который включает прототип , и либо прототип заканчивается на эллипсис ( , ... ), либо типы аргументов после продвижения по службе не совместимы с типами параметров, поведение не определено.
  4. Если функция определена с типом, который не включает прототип , а типы аргументов после продвижения по службе не совместимы с типами параметров после продвижения по службе, поведение не определено, за исключением следующих случаев:
    • один продвинутый тип – это целочисленный тип со знаком, другой продвинутый тип – это соответствующий целочисленный тип без знака, и значение представляется в обоих типах;
    • оба типа являются указателями на квалифицированные или неквалифицированные версии типа символа или void .

Пункт 7

  1. Если выражение, которое обозначает вызываемую функцию, имеет тип, который включает прототип , аргументы неявно преобразуются, как если бы по назначению, типам соответствующих параметров, считая тип каждого параметра безусловной версией объявленного тип.
  2. Обозначение многоточия в деклараторе прототипа функции вызывает преобразование типа аргумента для остановки после последнего объявленного параметра. Продвижение аргументов по умолчанию выполняется по завершающим аргументам.

Пункт 8

  1. Никакие другие преобразования не выполняются неявно; в частности, количество и типы аргументов не сравниваются с количеством параметров в определении функции, которое не включает декларатор прототипа функции .

Что я знаю

  • Активные объявления по умолчанию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 

Вопрос

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

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

    Ответ на утвердившийся AProgrammer – это настоящие товары.

    Для тех из вас, кто задается вопросом, почему все так: в темные века до 1988 года не было такого понятия, как прототип функции в classическом «K & R» C, и были объявлены промо-акции по умолчанию, потому что (а) «бесплатно», так как он больше не обязан помещать байты в регистр, чем положить слово в регистр, и (б) сократить возможные ошибки при передаче параметров. Эта вторая причина никогда не сокращала его, поэтому внедрение прототипов функций в 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) { /* ... */ } 

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

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

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

     void func(); 

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

     void func(a, b, c) int a; char b; float c; { /* ... */ } 
    Давайте будем гением компьютера.