我有一个使用函数function_1
的文件e1。在我用file1编译的一个文件e2中,我将函数定义为int function_1(char a)
。在我的文件1中,我有原型线void function_1(char a)
。当我编译和测试时,一切正常,没有任何警告。为什么这样可以?如果两种方式都有效,为什么还要要求原型函数类型呢?
这是因为编译器对给定的单个文件进行了所有检查,并且编译结果仅包含对名称function_1
的引用。链接器不检查任何类型,它只是连接名称。
C使用类型有两个目的:
- 用于一致性检查(防止程序员(某些)明显的错误)
- 用于代码生成(类型告诉编译器为变量保留多少内存,生成哪些代码来访问它们等)
所有这些都是一次完成一个编译单元。(编译单元基本上是一个。c文件和它包含的所有头文件。)
可以在不同的编译单元中用不同的类型声明相同的名称。(因为编译器一次只考虑一个单元,所以它不会捕获这个。)
但这意味着你有两段代码,它们对程序处理的实体类型有不同的想法。这可能会在运行时导致有趣和令人兴奋的bug。具体细节取决于您的平台/ABI。
例如,在x86上float
和int
具有相同的大小(4字节)。你可以定义一个函数
float foo(void) { return 42.0f; }
文件A中的并声明为
int foo(void);
文件b中的
,您可能期望结果程序大部分工作,除了它会将42.0f
的位模式重新解释为整数。但事实并非如此:调用约定指定int
在%eax
寄存器中返回,而float
使用%st0
浮点寄存器。可能的结果是,A将把42.0f
放在%st0
中,但B将把%eax
中的任何内容作为foo
的返回值。
(这可能会以更有趣的方式出错:例如,您可以在一个文件中使用float bar[] = { ... };
,而在另一个文件中使用void bar(void);
。这可能会导致第二个文件将浮点数组的内容解释为机器码(在调用bar()
时执行)
这就是为什么编译器需要知道形参和返回值的类型:这样它就可以为函数调用生成正确的代码(将参数放在函数期望它们的地方,并从函数放置它的地方检索返回值)。
但不幸的是,它不能交叉检查多个文件中的声明,而是依赖于程序员的正确判断。这就是为什么在头文件中声明你的"导出"(或"公共")函数(即那些不标记为static
)是最佳实践,该头文件由定义函数的文件和这些函数的用户都包含。这样你就不需要手动声明你想调用的函数,如果你这样做了,任何类型不匹配都将被捕获为无效/不兼容的重新声明。