1(为什么我们要向编译器提供两次相同的信息?2(这不是多余的吗?3( 我们什么时候应该遵守这个规则?4( 我们什么时候可以省略这个双重声明释义
void fx(void);
void fx ( void ){
printf("Hello Worldn");
}
int main(void)
{
fx();
}
1(为什么我们要向编译器提供两次相同的信息?
当C首次设计时,计算机非常有限(例如,以KiB而非MiB或GiB测量的RAM,具有<1MHz时钟的单个CPU,而具有>1GHz时钟的多个CPU,等等(。有很多技巧可以解决这个问题(将"编译"拆分为多个阶段,将"程序"拆分为几个编译单元,以小块形式读取文件,使整个文件不在RAM中,等等(。
其中一个技巧是一次性解析源文件
int foo(int x) {
return bar(x);
}
int bar(int x) {
return x;
}
编译器不会有任何线索;CCD_ 1";当其解析"是"时;CCD_ 2";(因为它直到稍后才会看到"bar()
"(。解决方案是声明——允许程序员在定义事物之前声明它们,这样编译器就可以完成一次操作,而不会被它还没有看到的东西弄糊涂。
- 我们什么时候可以省略这个双重声明和定义
如果编译器在使用该定义之前会看到该定义,则可以省略该声明。例如,这很好:
int bar(int x) {
return x;
}
int foo(int x) {
return bar(x);
}
2(这不是多余的吗?
它是多余的,但根据C语言规范,它是必要的,因为它在50多年前是必要的(使编译器在"极度资源受限"的计算机上实用(;尽管它已经30多年没有意义了,而且更新的语言也不需要它。
在您的情况下,不需要它。
但在这种情况下:
double fx(double x);
int main(void)
{
printf("%fn", fx(4.0));
printf("%fn", dx(4.0));
}
double fx(double x)
{
return x * x;
}
double dx(double x)
{
return x * x;
}
<source>: In function 'main':
<source>:21:20: warning: implicit declaration of function 'dx'; did you mean 'fx'? [-Wimplicit-function-declaration]
21 | printf("%fn", dx(4.0));
| ^~
| fx
<source>: At top level:
<source>:29:8: error: conflicting types for 'dx'; have 'double(double)'
29 | double dx(double x)
| ^~
<source>:21:20: note: previous implicit declaration of 'dx' with type 'int()'
21 | printf("%fn", dx(4.0));
https://godbolt.org/z/6ffWf9xG8
一般来说,像这样的功能原型
double fx(double);
1(为什么我们要向编译器提供相同的信息时间?
告诉编译器,代码或库函数fx
中的某个位置已经或将被定义,它接受一个double参数并返回double。编译器可以发出正确的代码来调用此函数,传递参数并使用返回值
2(这不是多余的吗
在您的示例中是这样。如果在函数调用之前函数的定义未知,则不是这样。
- 我们什么时候应该遵守这个规则
我已经在上面解释过了。
- 我们什么时候可以省略这个双重声明和定义
当函数在同一编译单元中定义时,和它在第一次调用之前(作为.c文件中的位置(定义。
一个没有正文的函数声明,如void fx(void);
,被称为原型,其目的是通知编译器存在一个具有某些返回类型、名称和可选参数集的函数,它可以在编译期间或之后链接时在其他地方找到这些函数。这些是语言的一部分,因为它们允许程序员模块化地设计软件。
声明函数原型可以防止编译器在调用尚未看到其定义的函数时抱怨,例如:
#include <stdio.h>
int foo(int in); //Without this the compiler will complain and/or refuse to compile
int main(){
printf("%dn",foo(7));
}
int foo(int in){
return in + 1;
}
此外,上面示例的第一行显示#include <stdio.h>
,它告诉编译器包含C标准io头文件。stdio.h
包含printf
的原型,其告诉一旦到了链接程序的时间,它将能够找到int printf(const char*,...);
形式的函数。
或者,您可以编写单独的文件";foo.c"foo.h";以及";main.c";对于更模块化的方法,比如:
main.c
#include <stdio.h>
#include "foo.h" //Include .h file to get prototype
int main(){
printf("%dn",foo(7));
}
foo.h
#ifndef FOO_H
#define FOO_H
int foo(int in); //Prototype of foo()
#endif
foo.c
#include "foo.h"
int foo(int in){ //Declatation of foo()
return in + 1;
}
然后,您可以将foo.c编译到对象文件中,并将其与main.c一起传递给编译器,比如:
gcc -c foo.c
gcc -o main main.c foo.o
如果你不想使用原型,你不会被迫使用,但如果你选择不使用它们,你将被要求在程序中的每个函数被另一个函数调用之前声明它。