Подтвердить что ты не робот

Func() vs func (void) в c99

void func() На практике пустой параметр означает, что любой аргумент принят.

void func(void) не принимает аргументов.

Но в стандарте C99 я нахожу такие строки:

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

в соответствии со стандартом, func() и func(void) совпадают?

4b9b3361

Ответ 1

TL; DR

В объявлениях

void func1();     // obsolescent
void func2(void);

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

В определениях

void func1() { }     // obsolescent

и

void func2(void) { }
  • Первая объявляет и определяет функцию func1, у которой нет параметров и нет прототипа

  • Последний объявляет и определяет функцию func2 с прототипом, который не имеет параметров.

Эти два ведут себя отчетливо, в то время как компилятор C должен печатать диагностическое сообщение при вызове прототипированной функции с неправильным количеством аргументов, не нужно делать это при вызове функции без прототипа.

I.e, учитывая приведенные выше определения

func1(1, 2, 3); // need not produce a diagnostic message
func2(1, 2, 3); // must always produce a diagnostic message 
                // as it is a constraint violation

Однако оба вызова являются незаконными в строго согласованных программах, поскольку они явно undefined по сравнению с 6.5.2.2p6.

Кроме того, пустые круглые скобки считаются устаревшей особенностью:

Использование деклараторов функций с пустыми скобками (не деклараторы типов параметров прототипа) является устаревшей функцией.

и

Использование определений функций с отдельными идентификаторами параметров и списками деклараций (а не тип и тип идентификатора прототипа) является устаревшей функцией.

Подробнее

Существует 2 взаимосвязанных, но разных понятия: параметры и аргументы.

  • аргументы - это значения, переданные в функцию.

  • параметры - это имена/переменные внутри функции, которые заданы значениями аргументов при вводе функции

В следующем отрывке:

int foo(int n, char c) {
    ...
}

...

    foo(42, ch);

n и c являются параметрами. 42 и ch являются аргументами.

Приведенная выдержка относится только к параметрам функции, но ничего не говорит о прототипе или аргументах функции.


Объявление void func1() означает, что функция func1 может быть вызвана с любым числом аргументов, то есть информация о количестве аргументов не указана (в качестве отдельного объявления C99 указывает это как "функция без спецификации параметров", тогда как объявление void func2(void) означает, что функция func2 вообще не принимает никаких аргументов.

Цитата в вашем вопросе означает, что в определении функции, void func1() и void func2(void) оба сигнализируют им, что нет параметров, то есть имена переменных, которые установлены на значения аргументов когда функция введена. void func() {} контрастирует с void func();, первый из них заявляет, что func действительно не принимает никаких параметров, тогда как последний является объявлением для функции func, для которой не указаны ни параметры, ни их типы (объявление без прототипа).

Однако они все же отличаются по определению в том, что

  • Определение void func1() {} не объявляет прототип, тогда как void func2(void) {} делает, потому что () не является списком типов параметров, тогда как (void) является списком типов параметров (6.7.5.3.10):

    Частный случай неименованного параметра типа void как единственного элемента в списке указывает, что функция не имеет параметров.

    и далее 6.9.1.7

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

    Объявление определения функции для func1 не содержит список типов параметров, и поэтому функция не имеет прототипа.

  • void func1() { ... } все равно можно вызывать с любым количеством аргументов, тогда как ошибка времени компиляции вызывает void func2(void) { ... } с любыми аргументами (6.5.2.2):

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

    (акцент мой)

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


Однако, если количество аргументов не равно числу параметров, поведение undefined 6.5.2.2p6:

Если выражение, которое обозначает вызываемую функцию, имеет тип, который не включает прототип, [...] Если количество аргументов не равно числу параметров, поведение undefined.

Таким образом, теоретически совместимый компилятор C99 также допускает ошибку или диагностику предупреждения в этом случае. Использование StoryTeller дает доказательства того, что clang может диагностировать это; однако мой GCC, похоже, не делает этого (и для этого также может потребоваться совместимость с каким-то старым неясным кодом):

void test() { }

void test2(void) { }

int main(void) {
    test(1, 2);
    test2(1, 2);
}

Когда вышеприведенная программа скомпилирована с помощью gcc -std=c99 test.c -Wall -Werror, вывод:

test.c: In function ‘main’:
test.c:7:5: error: too many arguments to function ‘test2’
     test2(1, 2);
     ^~~~~
test.c:3:6: note: declared here
 void test2(void) { }
      ^~~~~

То есть аргументы вообще не проверяются параметрами функции, декларация которых в определении не прототипирована (test), тогда как GCC считает ее ошибкой компиляции, чтобы указать любые аргументы прототипированной функции ( test2); любая соответствующая реализация должна диагностировать это, поскольку это нарушение ограничения.

Ответ 2

Значительная часть цитаты выделена жирным шрифтом ниже:

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

Итак, когда список параметров пуст для функции с ее телом, они одинаковы. Но это всего лишь объявление функции.

void function1(); // No information about arguments
void function2(void); // Function with zero arguments

void function3() {
    // Zero arguments
}

void function4(void) {
    // Zero arguments
}

Ответ 3

в соответствии со стандартом, func() и func (void) одинаковы?

Нет. func(void) говорит, что функция вообще не принимает аргументов; тогда как func() говорит, что функция принимает неопределенное количество аргументов. Оба действительны, но стиль func() устарел и не должен использоваться.

Это артефакт от pre-standard C. C99 отметил это как устаревшее.

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

Использование деклараторов функций с пустыми скобками (не деклараторы типов параметров прототипа) является устаревшей функцией.

Как и в случае С11, он по-прежнему остается устаревшим и не удаляется из стандарта.

Ответ 4

Пустой список параметров внутри определения функции означает, что он не содержит прототипа и не имеет каких-либо параметров.

C11 §6.9.1/7 Определения функций (акцент в текущих котировках мой)

Объявление в определении функции указывает имя определяемой функции и идентификаторов ее параметров. Если declarator содержит список типов параметров, список также указывает типы всех параметров; такой декларатор также служит прототип функции для последующих вызовов одной и той же функции в том же единица перевода.

Вопрос спрашивает:

в соответствии со стандартом, func() и func(void) совпадают?

Нет. Существенное различие между void func() и void func(void) заключается в их вызовах.

C11 §6.5.2.2/2 Функциональные вызовы (в разделе ограничений):

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

Обратите внимание, что параметры ≠ аргументов. Функция не может содержать никаких параметров, но может иметь несколько аргументов.

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

Однако технически это undefined поведение, чтобы вызвать такую ​​функцию с хотя бы одним аргументом (см. Antti Haapala комментарии).

C11 §6.5.2.2/6 Функциональные вызовы (в разделе семантики):

Если количество аргументов не равно числу параметров, поведение undefined.

Следовательно, разница тонкая:

  • Когда функция определена с помощью void, она не будет компилироваться, когда количество аргументов не совпадает с параметрами (вместе с их типами) из-за нарушения constaint (§6.5.2.2/2). Такая ситуация требует диагностического сообщения от соответствующего компилятора.
  • Если он задан с пустыми параметрами, он может или не может компилироваться (нет требования к диагностическому сообщению от соответствующего компилятора), однако UB вызывает такую ​​функцию.

Пример:

#include <stdio.h>

void func1(void) { puts("foo"); }
void func2()     { puts("foo"); }

int main(void)
{
    func1(1, 2); // constraint violation, it shouldn't compile
    func2(3, 4); // may or may not compile, UB when called
    return 0;
}

Обратите внимание, что оптимизирующий компилятор может отключить аргументы в таком случае. Например, таким образом Clang компилирует вышеуказанный код (исключая вызов func1) с помощью -01 на x86-64 в соответствии с соглашениями вызова SysV ABI:

main:                                   # @main
        push    rax          ; align stack to the 16-byte boundary
        call    func2        ; call func2 (no arguments given)
        xor     eax, eax     ; set zero as return value
        pop     rcx          ; restore previous stack position (RSP)
        ret