C99 中的 func() 与 func(void)
func() vs func(void) in C99
void func()
实际上,空参数意味着接受任何参数。
void func(void)
不接受参数。
但在标准 C99 中,我发现这样的行:
6.7.5.3 Function declarators (including prototypes)
14 An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
按照标准,func()
和func(void)
是一样的吗?
引用的重要部分在下面以粗体突出显示:
6.7.5.3 Function declarators (including prototypes) 14 An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
所以,当参数列表为空的函数及其主体时,它们是相同的。但它只是一个函数的声明。
void function1(); // No information about arguments
void function2(void); // Function with zero arguments
void function3() {
// Zero arguments
}
void function4(void) {
// Zero arguments
}
according to the standard, func() and func(void) is the same?
没有。 func(void)
表示该函数根本不需要 没有 个参数;而 func()
表示该函数采用未指定数量的参数。两者都有效,但 func()
样式已过时,不应使用。
这是来自 pre-standard C 的工件。C99 将其标记为已过时。
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.
从 C11 开始,它仍然过时并且没有从标准中删除。
函数定义中的空参数列表意味着它不包含原型,也没有任何参数。
C11 §6.9.1/7 函数定义
(强调正在进行的报价是我的)
The declarator in a function definition specifies the name of the
function being defined and the identifiers of its parameters. If the
declarator includes a parameter type list, the list also specifies the
types of all the parameters; such a declarator also serves as a
function prototype for later calls to the same function in the same
translation unit.
问题问:
according to the standard, func()
and func(void)
is the same?
没有。 void func()
和void func(void)
的本质区别在于调用。
C11 §6.5.2.2/2 函数调用(在约束部分):
If the expression that denotes the called function has a type that
includes a prototype, the number of arguments shall agree with the
number of parameters. Each argument shall have a type such that its
value may be assigned to an object with the unqualified version of the
type of its corresponding parameter.
注意参数≠参数。该函数可以不包含任何参数,但它可以有多个参数。
由于用空参数定义的函数不引入原型,因此不会根据调用对其进行检查,因此理论上它可以提供任意 个参数。
然而,它在技术上是一个 undefined behavior (UB) to call such function with at least one argument (see )。
C11 §6.5.2.2/6 函数调用(在语义部分):
If the number of arguments does not equal the number of parameters,
the behavior is undefined.
因此,区别很微妙:
- 当使用
void
定义函数时,如果参数数量与参数(及其类型)不匹配,则不会编译,因为违反约束(§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;
}
请注意,optimizing compiler may cut off the arguments in such a case. For instance, this is how Clang 根据 SysV ABI 调用约定在 x86-64 上使用 -01
编译上述代码(不包括 func1
的调用):
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
TL;DR
在声明中,
void func1(); // obsolescent
void func2(void);
行为完全不同。第一个声明一个没有任何原型的函数——它可以接受任意数量的参数!而后者声明了一个带有原型的函数,它没有参数也不接受任何参数。
在定义中
void func1() { } // obsolescent
和
void func2(void) { }
前者声明并定义了一个函数func1
,没有参数,没有原型
后者声明并定义了一个函数func2
,其原型没有参数
这两者的行为截然不同,因为 C 编译器 必须 在调用参数数量错误的原型函数时打印诊断消息,而 不需要 在调用没有原型的函数时这样做。
即,给定上述定义
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
但是 这两个 调用在 strictly-conforming 程序中都是非法的,因为根据 6.5.2.2p6.
它们是明确未定义的行为
此外,空括号被认为是过时的功能:
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.
和
The use of function definitions with separate parameter identifier and declaration lists (not prototype-format parameter type and identifier declarators) is an obsolescent feature.
详细
有 2 个相关但不同的概念:参数和自变量。
参数是传递给函数的值。
参数是函数中的names/variables,在函数进入时设置为参数的值
以下摘录:
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)
都表示没有 parameters,即 在输入函数时设置为 arguments 值的变量名称。 void func() {}
与 void func();
形成对比,前者声明 func
确实不带参数,而后者是函数 func
的声明,其中 参数也指定了它们的类型(没有原型的声明)。
然而,它们的不同之处在于 definition-wise
定义void func1() {}
没有声明原型,而void func2(void) {}
有,因为()
不是参数类型列表,而(void)
是参数类型列表(6.7.5.3.10):
The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
还有 6.9.1.7
If the declarator includes a parameter type list, the
list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit. If the declarator includes an identifier list, the types of the parameters shall be declared in a following declaration list. In either case, the type of each parameter is adjusted as described in 6.7.5.3 for a parameter type list; the resulting type shall be an object type.
func1
的函数定义声明符 不 包含 参数类型列表 ,因此该函数不包含没有原型。
void func1() { ... }
仍然可以使用任意数量的参数调用,而使用任何参数调用 void func2(void) { ... }
是 compile-time 错误 (6.5.2.2) :
If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.
(强调我的)
这是一个约束,根据标准,符合标准的实现必须显示至少一条关于此问题的诊断消息.但是由于 func1
没有原型,因此不需要符合要求的实现来生成任何诊断。
但是,如果参数的数量不等于参数的数量,行为是未定义的 6.5.2.2p6:
If the expression that denotes the called function has a type that does not include a prototype, [...] If the number of arguments does not equal the number of parameters, the behavior is undefined.
因此在理论上,符合 C99 标准的编译器也可以在这种情况下出错或诊断警告。 provided evidence that clang might diagnose this;然而,我的 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 将指定任何参数视为 compile-time 错误原型函数的参数 (test2
);任何符合规范的实现 必须 对此进行诊断,因为它违反了约束条件。
void func()
实际上,空参数意味着接受任何参数。
void func(void)
不接受参数。
但在标准 C99 中,我发现这样的行:
6.7.5.3 Function declarators (including prototypes)
14 An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
按照标准,func()
和func(void)
是一样的吗?
引用的重要部分在下面以粗体突出显示:
6.7.5.3 Function declarators (including prototypes) 14 An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
所以,当参数列表为空的函数及其主体时,它们是相同的。但它只是一个函数的声明。
void function1(); // No information about arguments
void function2(void); // Function with zero arguments
void function3() {
// Zero arguments
}
void function4(void) {
// Zero arguments
}
according to the standard, func() and func(void) is the same?
没有。 func(void)
表示该函数根本不需要 没有 个参数;而 func()
表示该函数采用未指定数量的参数。两者都有效,但 func()
样式已过时,不应使用。
这是来自 pre-standard C 的工件。C99 将其标记为已过时。
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.
从 C11 开始,它仍然过时并且没有从标准中删除。
函数定义中的空参数列表意味着它不包含原型,也没有任何参数。
C11 §6.9.1/7 函数定义 (强调正在进行的报价是我的)
The declarator in a function definition specifies the name of the function being defined and the identifiers of its parameters. If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit.
问题问:
according to the standard,
func()
andfunc(void)
is the same?
没有。 void func()
和void func(void)
的本质区别在于调用。
C11 §6.5.2.2/2 函数调用(在约束部分):
If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.
注意参数≠参数。该函数可以不包含任何参数,但它可以有多个参数。
由于用空参数定义的函数不引入原型,因此不会根据调用对其进行检查,因此理论上它可以提供任意 个参数。
然而,它在技术上是一个 undefined behavior (UB) to call such function with at least one argument (see
C11 §6.5.2.2/6 函数调用(在语义部分):
If the number of arguments does not equal the number of parameters, the behavior is undefined.
因此,区别很微妙:
- 当使用
void
定义函数时,如果参数数量与参数(及其类型)不匹配,则不会编译,因为违反约束(§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;
}
请注意,optimizing compiler may cut off the arguments in such a case. For instance, this is how Clang 根据 SysV ABI 调用约定在 x86-64 上使用 -01
编译上述代码(不包括 func1
的调用):
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
TL;DR
在声明中,
void func1(); // obsolescent
void func2(void);
行为完全不同。第一个声明一个没有任何原型的函数——它可以接受任意数量的参数!而后者声明了一个带有原型的函数,它没有参数也不接受任何参数。
在定义中
void func1() { } // obsolescent
和
void func2(void) { }
前者声明并定义了一个函数
func1
,没有参数,没有原型后者声明并定义了一个函数
func2
,其原型没有参数
这两者的行为截然不同,因为 C 编译器 必须 在调用参数数量错误的原型函数时打印诊断消息,而 不需要 在调用没有原型的函数时这样做。
即,给定上述定义
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
但是 这两个 调用在 strictly-conforming 程序中都是非法的,因为根据 6.5.2.2p6.
它们是明确未定义的行为此外,空括号被认为是过时的功能:
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.
和
The use of function definitions with separate parameter identifier and declaration lists (not prototype-format parameter type and identifier declarators) is an obsolescent feature.
详细
有 2 个相关但不同的概念:参数和自变量。
参数是传递给函数的值。
参数是函数中的names/variables,在函数进入时设置为参数的值
以下摘录:
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)
都表示没有 parameters,即 在输入函数时设置为 arguments 值的变量名称。 void func() {}
与 void func();
形成对比,前者声明 func
确实不带参数,而后者是函数 func
的声明,其中 参数也指定了它们的类型(没有原型的声明)。
然而,它们的不同之处在于 definition-wise
定义
void func1() {}
没有声明原型,而void func2(void) {}
有,因为()
不是参数类型列表,而(void)
是参数类型列表(6.7.5.3.10):The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
还有 6.9.1.7
If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit. If the declarator includes an identifier list, the types of the parameters shall be declared in a following declaration list. In either case, the type of each parameter is adjusted as described in 6.7.5.3 for a parameter type list; the resulting type shall be an object type.
func1
的函数定义声明符 不 包含 参数类型列表 ,因此该函数不包含没有原型。void func1() { ... }
仍然可以使用任意数量的参数调用,而使用任何参数调用void func2(void) { ... }
是 compile-time 错误 (6.5.2.2) :If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.
(强调我的)
这是一个约束,根据标准,符合标准的实现必须显示至少一条关于此问题的诊断消息.但是由于
func1
没有原型,因此不需要符合要求的实现来生成任何诊断。
但是,如果参数的数量不等于参数的数量,行为是未定义的 6.5.2.2p6:
If the expression that denotes the called function has a type that does not include a prototype, [...] If the number of arguments does not equal the number of parameters, the behavior is undefined.
因此在理论上,符合 C99 标准的编译器也可以在这种情况下出错或诊断警告。
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 将指定任何参数视为 compile-time 错误原型函数的参数 (test2
);任何符合规范的实现 必须 对此进行诊断,因为它违反了约束条件。