我们打开编译器用C语言/C++等语法写成的程序,仅仅是一段文本,如果想让计算机去运行这段代码,必须先进行翻译,翻译成计算机认识的二进制机器语言。
大家有没有想过为什么偏偏是二进制呢?
答案很简单:组成计算机的各种组件,只认识二进制。
具体的翻译细节,大家可以看如下这篇博客:
程序的编译链接
接下来主要讲述在整个翻译的过程,如何在linux
下用gcc
指令完成翻译,还有翻译过程的每一步的指令
直接翻译:
首先我们写一段C语言代码:
test.c
#include
#define M 20
int main()
{
printf("%d\n",M);
printf("hello C1!\n");
// printf("hello C2!\n");
// printf("hello C3!\n");
// printf("hello C4!\n");
printf("hello C5!\n");
printf("hello C6!\n");
#ifdef DEBGU
printf("hello debug!");
#else
printf("hello release!\n");
#endif
return 0;
}
使用如下指令:
gcc test.c -o mytest
如果不加-o mytest
,将默认生成一个名为a.out的可执行程序
-o
指令用于指定生成可执行程序的名称
执行如上代码,将获得一个名为mytest的可执行程序
执行程序:
./mytest
通过如上指令,可执行mytest程序
预处理过程包括:
如何在Linux
下用gcc执行上面的过程呢?
gcc -E test.c -o test.i
-E
指令表示将test.c文件进行预处理操作
-o
表示将处理结果存储到test.i文件中去
如上,左侧是源文件,右侧是预处理过的文件,
可以看到,确实做了如:引头文件、宏替换、去注释、条件编译等操作
这里我们着重说一下引头文件这一步
这一步的操作无非就是找到头文件,将其中的内容拷贝到当前文件中
这里<>
表示从库目录里找头文件
一般在Linux
当中,我们安装的头文件、库都在如下路径中
/usr/include/
当然,不排除它将来安装到其他目录下
既然要在翻译的时候要找到对应的头文件,
那么编译器内部都必须通过以一定的方式,知道你包含的头文件所在的路径
所以如上就是gcc自己的默认搜索路径
那么,之前我们遇到的老的编译器无法支持新的语法,就可以解释:
并不是编译器不支持,是编译器配套的头文件、库没有这些方法
我们安装编译器的同时把对应的头文件、库也安装到了相应的目录下
将C语言翻译成汇编语言
gcc -S test.i -o test.s
这里-S
表示将test.i文件(可以不是预处理后的文件)进行编译后停下来
如下是编译后形成的汇编代码:
将汇编语言翻译成可重定位的二进制文件(.o/.obj)
gcc -c test.s -o test.o
-c: 从现在开始进行程序的翻译,当我们汇编结束后停下来
一个小问题,当前形成的这个二进制文件而可以执行吗?
显然,它不具备可执行的权限,那么如果强制增加可执行权限呢?
显然,还是不行,所以链接这一步是必须的
所有包含头文件的操作,本质是因为想使用头文件所声明的方法
那我们代码中需要的printf
的实现在哪里??
/lib64/libc*
那么如何链接呢?
gcc test.o -o mytest
这一步隐含链接我们自己的程序和库
这里设计一个重要的概念:函数库
头文件
内含函数的声明给我们提供了可以使用的方法
然而几乎所有的开发环境都有的语法提示,本质是通过头文件帮我们搜索的
库文件
提供了可以使用的方法的实行,以供链接,形成我们自己的可执行程序
库文件又分为动态库和静态库,分别对应动、静态链接
这里为了让大家感性认识一下动、静态链接的区别,给大家引个小故事,方便理解
《动态链接》
我们的主人公:我有个朋友是一个网瘾少年,学校里不允许带电子设备,所以作为一个刚入学的高一学生,一进学校就提前到学长那里打听好了,出校门往东500米就有一家网吧”极速码头“。他到了宿舍,睡了三十分钟觉,看了30分钟书,扣了20分钟手,然后就想去打游戏,计划说打完游戏就回来接着看书、上课。于是按照学长说的那个地方就去了,去了之后他让网管开一台机器,老板告诉他去40号机器,钱一交就去40号机痛快的打游戏了。打完游戏他一看表,完了时间到了,赶紧跑回去继续执行剩下的计划。
这其中他给自己定的这个计划,就相当于我们自己写的一行一行代码,都属于自己的操作,虽然学校里没有地方完成,但是刚入学的时候你已经跟学长问好了,他也暂时不去,但是他知道在哪,这个过程就叫链接,然后执行他的一项一项任务,即我们写的代码,走到打游戏这一步,就跳转过去执行,这个网吧就是我们所说的库,问网管的过程就相当于查库的过程,找到你所需要的40号函数,然后执行函数对应的方法,执行完毕再返回继续向后。像这样我们自己编写自己的程序,库一直在那里,只需要在链接的时候把我需要的方法和库中的位置通过地址的方式关联起来,这种方式就叫动态链接。
那么也同时存在这样一种情况:并非只有他一个人想打游戏,学校里还可能存在几百上千名躁动的少年,都想在合适的时候去上网,所以那个网吧就是被学校里所有少年共享的一个库。相当于整个系统有成百上千条命令,大家一旦想上网,一旦想去上网都可以使用,这就叫动态链接。
《静态链接》
我的另一个朋友呢,也是一个网瘾少年,只不过他的学校是一个管制不太严的学校,可以带电子设备,又恰好隔壁的网吧生意不景气,要破产卖电脑,于是他就买了一台带进了宿舍。同样他也定了一系列的计划,看书、扣手……打游戏,但是这回他不再需要出去上网,而是下床就直接开始打游戏,他旁边的张三李四等人也都有电脑,他们每一个人上网都是上自己的网,和别人没有关系,调的都是自己的方法,这就叫静态链接。
这里把网吧的电脑带到宿舍,就相当于我们把库中的,你所想要的方法拷贝到你的可执行程序当中,这种方法就叫静态。也就是说所有的程序每次有调用这个方法,都会在链接时提前进行拷贝,就都是调用自己的方法了。
动静态库的优缺点:
动态库:全校所有的学生要上网都要来这家网吧,如果有一天,这家网吧突然被警察局查封了,网吧一旦不存在,那全校所有的学生都无法完成这项任务。
所以动态链接的优点:大家共享一个库,可以节省资源
缺点:一旦库缺失,会导致几乎所有程序失效
静态库:将库中的相关代码,直接拷贝到自己的可执行程序当中
那么这些细节在gcc中如何体现呢?
还是之前的test.c
形成的可执行文件mytest
文件为例:
使用如下指令可查看当前程序的链接状态:
ldd mytest
大致可以看出它依赖的是一个C标准库。
还有一条指令可查看可执行程序的构成:
file mytest
可以看到,默认情况下形成的可执行程序是动态链接的
如果想进行静态链接,可以加 -static
选项
gcc test.c -o mytest2 -static
小贴士:
因为一般而言,系统都没有带静态库
yum -y install glibc-static
安装C语言静态库
yum -y install libc+±static
安装C++静态库
如下分别是动、静态链接生成的可执行程序
可以看出,仅仅调用了几个printf()
静态链接就大了很多。
所以一般情况我们更推荐使用动态链接
我们一般只需要使用gcc test.c -o mytest
这一条命令即可
如果想分步进行,它的顺序是ESc,恰好对应键盘左上角的按键。
所生成的文件后缀分别是iso,是镜像文件的后缀,可由此记忆