【Linux】gcc/g++使用
编译
我们知道,gcc只能编译C,g++既能编译C,也能编译C++。
由于两者的选项是相同的,这里我们使用gcc来说明。
这就是一个我们在linux中gcc编译一段代码后会自动生成一个a.out为名的可执行文件,然后我们./a.out
,就可以执行这段代码。
我们也可以对生成的可执行程序的名称进行指定:
gcc code.c -o mycode
另种写法:
gcc -o mycode.exe code.c
程序的翻译过程
预处理(进行宏替换)
- 宏替换
- 去注释
- 头文件展开
- 代码裁剪
gcc -E code.c -o code.i
这样可以让我们把预处理结果写入到一个临时文件code.i里。
-E
的作用是,开始进行程序翻译,在预处理做完时就停下来。
vim code.i
我们可以将其打开,发现我们源文件比code.i短了非常多:
我们可以打开这个系统自带的C语言开发环境。
可以看到这下面就包含了众多我们C语言中常见的头文件。
这也就是为什么我们的code.i这么大:因为头文件展开。
其实预处理完,我们的头文件就可以不需要了。
编译(生成汇编)
gcc -S code.i -o code.s
这样我们就可以只进行编译这一步。
这就是汇编语言了。
-S
是现在开始进行程序的翻译,但是把编译做完就停下。
汇编(生成机器可识别代码)
gcc -c code.s -o code.o
code.o这样的文件,我们把它叫做可重定位目标文件。在Windows中或者说在VS中,这种文件一般以XXX.obj的形式出现。
这种文件本身已经是二进制的了。
vim code.o
我们还是可以看:
可以看到,我们直接这样看事一堆乱码。
但现在这个文件还是无法执行的。
因为这仅仅是把我们写的代码编成了二进制,但是我们源文件中还有很多的库方法,还没有关联起来。需要经过链接。
-C
就是程序开始翻译,汇编完成就停下。
链接
gcc code.o -o code
这样就链接完了,形成了可执行程序code。
不过,这几个临时文件一般不会显示到磁盘上,而是在内存中就完成了。
怎么记住这几个选项呢?-ESc,是不是很像键盘左上角的键?
后缀是.iso,这个又怎么联想呢?镜像文件后缀一般是iso的。
如果我们gcc -c code.c
不带生成文件名,一般形成的就是原名以.o后缀:code.o
我们一般喜欢先统一把源文件变成.o
然后gcc code.o code1.0 code2.o -o code
然后把它们打包形成一个统一的可执行程序:
为什么喜欢这么做呢?
因为编译器在编译时不仅要形成可执行程序,有可能还要形成库,所谓库也就是把.o文件打了个包。
如果要形成库的话,就不需要形成可执行程序。
还有一个理由,在VS上也喜欢先变成.obj(相当于.o),最后再链接。
查看可执行程序依赖哪些库
ldd code
我们就可以查看可执行程序依赖哪些库。
我们的源文件里用到的一些函数如printf,实现在C标准库里,所以我们的程序依赖C标准库,而它就是libc.so
我们用的C语言库是2.17版本的。
在任何平台下,库分两类。
在linux下,动态库一般以.so结尾。Windows中的动态库一般以.dll结尾。
在linux下,静态库一般以.a结尾。Windows中的静态库一般以.lib结尾。
我们linux中用的ls这些命令,通过ldd /usr/bin/ls
,可以看到也依赖C标准库,所以可以看出这些命令是用C语言写的。
补充
条件编译
其实就是做代码裁剪
我们还可以这样(没定义M)
-D
可以让我们命令行级别地进行宏定义,所以可以看到,gcc code.c -o code -DM
是定义了M,所以就是专业版,下面的没有宏定义M,就是社区版。
也可以设置初始值:gcc code.c -o code -DM=100
编译器会把-DM=100
解释为#define M=100
,当做字符串插入我们的源文件再进行预处理。
条件编译的用途?
软件常分为社区版(免费)和专业版(收费),一般就是功能点差一些。其实公司内部只维护一份源代码,节省资源。发布时两者编译不同,功能拆分,条件编译来维护,进行代码裁剪。
linux操作系统也分为server服务器版,desktop版,虚拟机是后者。区别就是server版可能新增了一些开发工具,桌面相关的一些在编译时裁掉了。
还有,一些嵌入式设备不需要linux有那么多功能,可以裁掉。
linux的内核源代码也支持裁剪,不是由程序员手动去删的,是由条件编译来实现代码的动态裁剪的。
有的开发工具、应用软件既能在linux也能在Windows下跑,也是因为条件编译。发在哪个系统就把其他系统部分裁掉。
编程的本质就是在控制计算机。早期用的是计算机上的开关。
然后产生了打孔编程(二进制编程)。
然后发明了汇编语言。
这个助记符,也就是指令。
从汇编语言开始,就需要编译器这个东西了。
因为汇编语言也是文本,要由编译器映射成二进制。
有了汇编之后就慢慢有了C语言(70年代),C++,JAVA,GO等。
C语言也需要由C语言的编译器。也需要编译成二进制。
是直接把C语言变成二进制可执行程序,还是先翻译成汇编呢?
是后者。
- 因为把C语言变成汇编还是从文本到文本,难度较低。
- 另一方面汇编此时已经发展完善,只要将C语言翻译到汇编,汇编到二进制这部分工作就不需要做了。
这就是为什么我们翻译程序的过程中药先变成汇编语言,再变成二进制。
为什么有链接这一步?
我们不想在写程序前都还要写一遍printf等各种库函数的实现,所以就想要站在巨人的肩膀上,所以就需要库。
所以我们的代码编译好成为可重定位目标文件,然后和库进行链接,最后形成可执行。
(预处理展开的只是声明,因为头文件是公开的。头文件只有声明,没有实现。而形成可执行我们最终要的是方法。
C/C++是编译型语言,也为了让我们能进行库级别的开发,所以C/C++允许我们将头文件和源文件分开写。
其实写在一起更方便,但是分成头文件和源文件是因为C语言写的东西未来很多都要作为库,不想让人看到实现,但声明不得不给别人(得知道参数和返回类型)。)
说回编译器
C语言发明了,也需要有C语言的编译器来编译C语言,这个编译器至少要能把我们的语言翻译成汇编。
我们知道汇编语言也需要有编译器将其编译成二进制。那么,这个编译器自己应该用什么语言来写呢?
第一版编译汇编语言的编译器不可能用汇编写,是用二进制版的。
此时我们就可以编译汇编语言了。
但是编译器本身也是个软件,也有不同的版本。既然已经有了汇编语言和编译汇编语言的二进制版编译器,这时我们就可以用汇编语言来写一个汇编编译器了。
我们把用汇编语言写的这个汇编编译器再用二进制版的编译器编译一下,此后就不再需要二进制版本的汇编编译器了。我们就有了汇编语言写的汇编编译器。
这叫做编译器的自举过程。
本文结束。