为什么声明的原型函数类型不需要与实际函数的类型相匹配?

时间:2016-10-28 05:06:44

标签: c prototype

我有一个使用函数function_1的file1。在我用file1编译的file2中,我将函数定义为int function_1(char a)。在我的file1中,我有原型行void function_1(char a)。当我编译和测试时,一切正常,我没有任何警告。为什么这样可以?为什么甚至要求原型函数类型,如果它以任何方式工作?

编辑:我很欣赏这些警告,但只有melpomene甚至试图回答被问到的问题,尽管我对答案不满意。如果链接器没有检查类型,为什么它甚至应该成为声明的一部分呢?

1 个答案:

答案 0 :(得分:0)

这是因为编译器对它已经给出的单个文件进行了所有检查,并且编译结果只包含对名称function_1的引用。链接器不检查任何类型,它只是连接名称。

C使用类型有两个目的:

  1. 进行一致性检查(以防止(某些)程序员明显的错误)
  2. 代码生成(类型告诉编译器为变量保留多少内存,生成访问它们的代码等)。
  3. 所有这一切都是一次完成一个编译单元。 (编译单元基本上是.c文件及其包含的所有头文件。)

    您可以在不同的编译单元中声明具有不同类型的相同名称。 (因为编译器一次只考虑一个单元,所以它不会抓住它。)

    但这意味着您有两段代码,对程序正在处理的实体类型有不同的想法。这可以在运行时产生有趣和令人兴奋的错误。具体细节取决于您的平台/ ABI。

    例如,在x86上,floatint具有相同的大小(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()时执行)。)

    这就是编译器需要知道参数类型和返回值的原因:它可以为函数调用生成正确的代码(将参数放在函数期望的位置,并检索返回值)从函数所在的位置开始。)

    但不幸的是,它并没有在多个文件中交叉检查声明,依靠程序员来做对。这就是为什么最佳做法是宣布你的'#34;导出" (或#34; public")函数(即那些未标记为static的函数)在头文件中,该文件既包含定义函数的文件,也包含在这些函数的用户中。这样您就不需要手动声明要调用的函数,如果您这样做,任何类型不匹配都将被捕获为无效/不兼容的重新声明。