函数声明与定义 C

Function declaration vs. definition C

我有一个简单的代码,我的函数在主函数之前声明如下:

int function1();
int function2();

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

在我的主要功能之后,我有功能定义:

当我像这样在 main 之前声明函数时,有什么不同吗?

int function1(int x, float y);
int function2(int x, float y);

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

是的,它们是不同的。

在第一个示例中,您只是告诉编译器函数的名称和 return 类型,而不是其预期参数。

在第二个示例中,您在调用函数之前告诉编译器函数的完整签名,包括 return 类型和预期参数。

第二种形式普遍更好,因为它可以帮助编译器在调用函数时参数类型或数量错误时发出警告。

另请注意,C 中的 int function() 是可以接受 any 个参数的函数,而不是接受 no 个参数的函数.为此,您需要一个明确的 void,即 int function(void)。这主要会绊倒那些从 C++.

来到 C 的人

另请参阅: Why does a function with no parameters (compared to the actual function definition) compile?

为了证明为什么第一个过时的形式在现代 C 中是错误的,下面的程序编译时没有警告 gcc -Wall -ansi -pedanticgcc -Wall -std=c11

#include<stdio.h>
int foo();

int main(int argc, char**argv)
{
  printf("%d\n", foo(100));
  printf("%d\n", foo(100,"bar"));
  printf("%d\n", foo(100,'a', NULL));
  return 0;
}

int foo(int x, int y)
{
  return 10;
}

更新: M&M 引起我的注意,我的示例使用 int 而不是 float 作为函数。我想我们都同意声明 int function1() 是错误的形式,但我关于此声明接受任何参数的说法并不完全正确。请参阅 Vlad 对相关规范部分的回答,解释为什么会这样。

在第一种情况下,main() 对每个参数执行整数提升和 floatdouble 提升。这些称为 "default argument promotions"。因此,您可能最终会通过传递 intdouble 而错误地调用函数,因为函数需要 intfloat.

有关详细信息,请参阅 Default argument promotions in C function calls 和答案。

是的,它们是不同的;第二个是正确,第一个完整是错误的。 GCC 5.2.1 完全拒绝编译它 是错误的。它对你有用只是 fluke:

/* this coupled with */
int function1();

int main() {
    /* this */
    function1(x, y);
}

/* and this one leads to undefined behaviour */
int function1(int x, float y) {
    /* ... */
}

在上面的代码中,声明int function1();没有指定参数类型(它没有原型),这在C11中被认为是obsolescent特性(和 C89、C99 一样)标准。如果调用那种函数,则对参数进行默认参数提升:int 按原样传递,但 float 提升为 double.

由于您的实际函数期望参数为 (int, float),而不是 (int, double),这将导致未定义的行为。即使您的函数期望 (int, double)y 是一个整数,或者说您使用 function1(0, 0); 而不是 function(0, 0.0); 调用它,您的程序仍然会有未定义的行为。幸运的是 GCC 5.2.1 注意到 function1 的声明和定义是冲突的:

% gcc test.c
test.c:9:5: error: conflicting types for ‘function1’
 int function1(int x, float y) {
     ^
test.c:9:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function1(int x, float y) {
 ^
test.c:1:5: note: previous declaration of ‘function1’ was here
 int function1();
     ^
test.c:12:5: error: conflicting types for ‘function2’
 int function2(int x, float y) {
     ^
test.c:12:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function2(int x, float y) {
 ^
test.c:2:5: note: previous declaration of ‘function2’ was here
 int function2();
     ^

并且编译器以错误代码退出,而我的 tcc 编译它很愉快,没有诊断,什么都没有。它只会产生损坏的代码。当然,如果您在头文件中有声明,而在不包含该声明的不同编译单元中有定义,那当然也是如此。


现在,如果编译器没有检测到这种情况,在运行时,任何事情 都可能发生,正如 未定义行为 所预期的那样。

例如,假设参数是在堆栈上传递的情况;在 32 位处理器上 intfloat 可以容纳 4 个字节,而 double 可以容纳 8 个字节;然后函数调用会将 x 推送为 int,将 y 推送为 double,即使它是 float - 调用者总共推送了 12 个字节,并且被叫方只期望 8.

在另一种情况下,假设您使用 2 个整数调用该函数。然后调用代码会将它们加载到整数寄存器中,但调用者会期望在浮点寄存器中有一个双精度值。浮点寄存器可能包含一个陷阱值,访问时会终止您的程序。

最糟糕的是,您的程序 可能 现在的行为 符合预期 ,因此包含一个 heisenbug 可能会导致问题您使用更新版本的编译器重新编译代码,或将其移植到另一个平台。

不同之处在于第二个代码片段中有一个函数原型,然后编译器检查参数的数量和类型是否与参数的数量和类型相对应。如果编译器发现不一致,它可能会在编译时发出错误。

如果没有第一个代码片段中的函数原型,则编译器会对每个参数执行默认参数提升,包括整数提升和 float 类型表达式到 double 类型的转换。如果在这些操作之后,提升参数的数量和类型与参数的数量和类型不对应,则行为未定义。编译器可能无法发出错误,因为函数定义可能在其他编译单元中。

这里引用了 C 标准(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.

6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:

— one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;

— both types are pointers to qualified or unqualified versions of a character type or void.

至于您的代码片段,如果第二个参数的类型为 double,那么代码将是格式良好的。但是,由于第二个参数的类型为 float,但相应的参数将被提升为类型 double,因此第一个代码片段具有未定义的行为。