龙空技术网

从零开始学习C语言丨从函数原型到编译过程

三玹CC 304

前言:

现在同学们对“c语言什么是编译”大概比较关怀,兄弟们都需要学习一些“c语言什么是编译”的相关文章。那么小编也在网摘上网罗了一些有关“c语言什么是编译””的相关文章,希望兄弟们能喜欢,同学们快快来学习一下吧!

本来准备学习C语言中的作用域,但是在学习的过程中,发现了新的名词,函数原型。

一时间就产生好奇,函数还有原型?什么是函数原型?藏着太多疑惑,心里痒痒,就先学习一下函数原型。

函数原型

这个名称看着似乎有点抽象,但它的另一个叫法或许更好理解,函数声明。

前面学过的变量是需要声明的,需要提供变量的类型、变量名称,编译器才知道怎么使用这个变量。

函数声明也是一样的,需要提供这个函数的返回值类型、函数名称、参数列表(重点是参数的类型)等相关信息。

目的是为了告诉编译器,这家伙长什么样,怎么用。具体他会做什么事,我稍后再跟你说,你别不认识就把人赶跑了。

声明格式:

int add(int a, int b);// 或者int add(int, int);

看到这个,或许有的人就有疑惑了,这个不就和函数定义差不多,就是少了函数实现的具体代码块。为什么还需要这个函数原型呢?

这就不得不说一下C语言编译了。

编译问题

C语言的程序是自上而下,顺序执行的。如果遇到未知的东西,程序就会报错。

就比如没有声明变量,就直接 a=3 这样赋值,那程序就会直接报错。因为编译器从上读到这里,不知道 a 是什么东西,突然就冒出来,就会以为是什么脏东西混进来了,然后马上终止程序。

函数也是相似的。

往往我们都是主函数写在前面,而其它函数定义是写在主函数之后。那么在程序执行的时候,编译器运行到主函数中的函数调用,就发现这又是个什么脏东西跑进来。

就比如这个例子:

#include<stdio.h>int main(){  helloWorld();  return 0;}void helloWorld(){  printf("Hello World!");}

运行结果:

可以看到,结果弹出了一堆警告,但是也有运行结果。这是为什么呢?

一开始,我也不太注意这些警告,以为有结果就行了,有问题也只是编辑器的问题。

但看了函数原型之后,才发现编辑器没问题,是我寄几的问题。

来看一下编译器给出的警告都是什么意思:

在 main 函数中:

第3行第5列:警告:函数 helloWorld 的隐式声明

...

第6行第6列:警告:helloWorld 类型冲突

...

第3行第5列:注意:之前隐含的 helloWorld 声明在这里

...

在C语言当中,如果程序在前面没有遇到所调用函数的声明或者定义,就会默认生成一个隐式声明。

int helloWorld();

所以不影响函数的最终运行,但是这样是存在隐患的。

隐式声明的函数是 int 类型的,但是我们最终定义的函数是 void 类型。所以就出现了第二个警告,类型发生了冲突。

那么也有人说,我把所有函数定义都写在函数原型的位置不就好了。

如果只有主函数调用这些函数,这样做无可厚非,但是不建议。

如果有多个函数,它们之前互相调用会发生什么情况呢?

来看一个案例:

#include<stdio.h>int func1(int a){    if(a > 3){        func2(a);    }    else{        return 0;    }}int func2(int a){    if(a<3){        func1(a);    }    else{        return 1;    }}int main(){  int x=1, y;  y = func1(x);  printf("%d", y)  return 0;}

这里定义的两个函数,无论哪一个在前,都会出现问题。

但是有了函数声明,哪个先定义,哪个先调用,这些问题就不用考虑了。

一般建议,书写C语言程序是将函数声明放在主函数前头,例如头文件(stdio.h),函数定义放在主函数后头。这样一来,可以让代码的结构看起来清晰明了。

而且使用者往往是不在意你函数具体是怎么实现的,只在乎你的函数有什么功能,怎么用,好用吗。

所以,把函数声明放在前面,是为了说明函数的重要信息。至于函数定义里面复杂的内容,别人不想看,也不想知道,就放在后面。

且在实际开发中,一个项目往往都是成千上万行,甚至百万级别的,都是常见的。

要是把所有内容都放在一个文件里,可想而知,内容是多么的庞大。一来检索起来非常麻烦,二来文件越来越大,打开的速度也就会越来越缓慢。

因此,就需要将代码进行拆分,归类到不同的文件里。

通常是将函数声明放在头文件(.h),函数定义放在源文件(.c),使用函数的时候只需要引入头文件,编译器就会在链接阶段找到函数文件。

或许对此,大家还是有点疑惑,函数声明和函数定义为什么可以拆分成两个文件?

这就涉及到C语言编译的过程,下面简单说一下C语言编译过程。

编译过程

C语言程序从我们写下,到运行出结果,一共经历了四个步骤。

第一步:预处理。

通过预处理器,完成删除注释、宏扩展、文件包含等。

注释只是人为的添加解释,为了方便阅读代码。对程序本身没有任何影响,所以会在这一部将注释删除掉。

宏扩展,使用 #define 指令定义的一些常量或表达式。

文件包含,即 #include 指令,将另一个文件引用到我们自己的文件当中。

预处理之后,会生成一个临时文件(.i)。

第二步:编译

这个阶段是由编译器完成的,帮助我们完成语法和语义的解析,生成由汇编代码组成的文件(.s)。

这个步骤会将我们源代码中的任何语法错误和警告,通过端口窗口告诉我们。

第三步:汇编

计算机是看不懂我们的文字,什么是英文,什么是数字,一概不懂。它只能识别机器语言,二进制码。

这个阶段就是帮我们将生成的汇编文件(.s)翻译成机器码指令,然后将这些指令打包形成目标文件(.o)。

第四步:链接

这一步帮我们完成了文件中所调用的各种函数、静态库和动态库的链接。也解释了函数原型和函数定义可以分开到两个文件里的问题。

完成后,会生成一个可执行文件(.exe),运行这个文件也就能获得我们想看到的结果。

最后

函数原型到编译过程的内容结束了。其中关于编译过程的内容,只是简单的涉猎,内容或多或少有些不准确的地方,或者看不明白的地方。希望大家可以在评论区指出,一起讨论下!

标签: #c语言什么是编译