【问题标题】:C order of function declarationsC 函数声明的顺序
【发布时间】:2024-01-05 18:28:01
【问题描述】:

这是一个示例代码,类似于我遇到的问题

    #include <stdio.h>

    int cube_then_square(int x){
        x = cube(x);
        return x*x;
    }

    int cube(int y){
        return y*y*y;
    }

    int main(int argc, char *argv[]){
        printf("5 cubed then squared is: %d\n", cube_then_square(5));
        return 0;
    }

所以编译器给了我一个未声明多维数据集的问题。所以有人可以解释一下这些函数在内存中的顺序,等等......它与将原型放在顶部和 main 之后的实现有什么不同。谢谢。

【问题讨论】:

  • 这与函数在内存中的放置顺序无关(这主要取决于链接器)。它是关于单遍编译器在调用该函数之前查看该函数的声明。 C++ 在某种程度上打破了“单程”假设,但只是在某种程度上。
  • @chris 不,他们不需要,它在被调用时被前向声明(如果这是 C)
  • @SteveCox,是的,我是在 C++ 的上下文中讨论的。看看标签是如何被删除的,我的评论也是如此。
  • 这个程序是合法的 C 并且在 gcc 中编译得很好。
  • 这通常会在新版本的 GCC 或 clang 中编译得很好,可以进行 2 次编译或前瞻,但不需要原型...

标签: c function prototype declaration


【解决方案1】:

存在隐式声明函数的警告,因为大多数现代 C 程序不使用隐式函数。

旧 C89 允许隐式声明所有内容。当你调用一个函数时,它被隐式声明为int func ()。 这适用于这种情况,因为您使用以下行隐式声明函数 int cube()

x = cube(x);

然后你定义函数int cube(int)int cube()int cube(int) 具有兼容的类型,所以这是一个很好的调用。

您真正遇到的麻烦是从隐式声明的函数调用不兼容的函数(这就是警告存在的真正原因)。 int cube(float) 是一个不兼容的函数类型,它肯定存在,如果你用隐式声明的函数调用它,你可能会期待一些非常奇怪的效果(读取未定义)。正如 mafso 提到的,严格的 C99 不再允许隐式声明的函数,这就是为什么许多编译器都包含警告的原因。

记住隐式声明的函数是不好的做法,但你应该知道这种情况的存在。

这里有一个小程序来演示隐式声明函数的弱点。它利用了您在 c 程序中期望的一些转换规则,这些转换规则突然消失了隐式声明的函数。

#include <stdio.h>

cube1(int x){return x*x*x;}
main(){float y = 9.; printf("%d\n%d\n", cube1(y), cube2(y));}
cube2(int x){return x*x*x;}

输出:

729
1

这些函数在 asm 中是相同的

00000000004004dc <cube1>:
  4004dc:   55                      push   %rbp
  4004dd:   48 89 e5                mov    %rsp,%rbp
  4004e0:   89 7d fc                mov    %edi,-0x4(%rbp)
  4004e3:   8b 45 fc                mov    -0x4(%rbp),%eax
  4004e6:   0f af 45 fc             imul   -0x4(%rbp),%eax
  4004ea:   0f af 45 fc             imul   -0x4(%rbp),%eax
  4004ee:   5d                      pop    %rbp
  4004ef:   c3                      retq   
0000000000400540 <cube2>:
  400540:   55                      push   %rbp
  400541:   48 89 e5                mov    %rsp,%rbp
  400544:   89 7d fc                mov    %edi,-0x4(%rbp)
  400547:   8b 45 fc                mov    -0x4(%rbp),%eax
  40054a:   0f af 45 fc             imul   -0x4(%rbp),%eax
  40054e:   0f af 45 fc             imul   -0x4(%rbp),%eax
  400552:   5d                      pop    %rbp
  400553:   c3                      retq  

但在调用点,从不为隐式调用执行从浮点到整数的预期转换。

【讨论】:

    【解决方案2】:

    编译器从上到下读取文件。当它到达函数时,它会检查它是否已经知道它。在这种情况下它没有看到函数cube(int),所以它返回一个错误。

    你可以做两件事: 1. 将函数cube 移到函数cube_then_square 之前。 2.你在cube_then_square之前创建一个前向声明:

    int cube(int y);
    

    【讨论】:

      【解决方案3】:

      在文件顶部的包含下,添加int cube(int y);

      你必须在使用之前声明一个函数。它现在还不需要定义,但在使用之前必须先声明,因为编译器会按顺序检查行。

      【讨论】:

        【解决方案4】:

        要解决这个问题,你可以在使用之前声明cube

        #include <stdio.h>
        
        int cube(int y){
            return y*y*y;
        }
        
        int cube_then_square(int x){
            x = cube(x);
            return x*x;
        }
        
        int main(int argc, char *argv[]){
            printf("5 cubed then squared is: %d\n", cube_then_square(5));
            return 0;
        }
        

        也可以在顶部添加函数头int cube(int y);

        #include <stdio.h>
        
        int cube(int y);
        
        int cube_then_square(int x){
            x = cube(x);
            return x*x;
        }
        
        int cube(int y){
            return y*y*y;
        }
        
        int main(int argc, char *argv[]){
            printf("5 cubed then squared is: %d\n", cube_then_square(5));
            return 0;
        }
        

        【讨论】:

        • 解决问题,检查。忘记问题是例子,检查。不解决问题,检查。只是添加一些幽默,记住不要太快!
        【解决方案5】:

        这些函数如何“放入内存”完全无关紧要。事实上,这绝不是在语言层面上暴露出来的。没关系。

        在组织实施文件时,您可以选择自上而下或自下而上的方式。

        在前一种情况下,首先定义上层函数。上层函数调用下层函数,这些函数稍后在文件中定义。为了使其正常工作,必须在第一次调用之前对低级函数进行额外的原型化(例如,在文件的最顶部)。 (形式上不需要原型,但至少需要一个声明。)

        在后一种情况下,首先定义低级函数,然后再定义高级函数。在这种方法中,定义本身已经用作原型,这意味着不需要额外的原型设计(除非您在函数之间具有循环调用依赖关系)。

        自上而下是一种维护性更高的方法,因为您必须使函数原型与其定义保持同步。

        您的示例中的内容看起来像是这两种方法的混合。 main(最高层函数)定义在底部,而cube_then_square定义在cube之前。这并没有什么问题,但要保持这种顺序,您必须在文件开头提供 cube 的原型。

        我个人更喜欢自下而上的方法。例如。在您的具体示例中,我会将 cube 的整个定义移至文件顶部。这将消除提供额外原型的需要。

        无论如何,无论您如何操作,生成的代码都没有有意义的差异。目标文件中函数的顺序可能会受到它们在源文件中的顺序的影响,但这从语言的角度来看是完全无关紧要的。

        【讨论】: