【Linux系列】打造你的数字车间:Linux 基础开发工具入门与精要 — gcc/g++ 编译
🫧 励志不掉头发的内向程序员:个人主页
✨️ 个人专栏: 《C++语言》《Linux学习》
🌅偶尔悲伤,偶尔被幸福所完善
👓️博主简介:
文章目录
前言
上一章节讲解了 vim 编辑器,但是还没有说明我们的文件怎么编译成可执行文件,本章节我们便来讲解说明,这也是一个非常重要的内容,我们一起来看看吧。
一、编译流程
我们编译一般涉及 4 个流程,每个流程都有其作用,缺一不可。
- 预处理(进行宏替换/去注释/条件编译/头文件展开等)
- 编译(生成汇编)
- 汇编(生成机器可识别代码)
- 链接(生成可执行文件或库文件)
二、gcc 编译
gcc编译的作用就是用来编译我们 C 语言的文件的。编译时我们可以直接编译成可执行文件,也可以按照编译流程一步一步的编译。
2.1、直接生成可执行程序的 2 种方式
- gcc [ 要编译的文件 ] -o [ 目标文件 ]
-rw-rw-r-- 1 zxl zxl 304 Sep 25 10:26 code.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.c -o mycode -rwxrwxr-x 1 zxl zxl 16000 Sep 25 11:24 mycode* zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ ./mycode hello world!- gcc -o [ 目标文件 ] [ 要编译的文件1 ] [ 要编译的文件2 ] [ 要编译的文件3 ] …
-rw-rw-r-- 1 zxl zxl 304 Sep 25 10:26 code.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -o mycode code.c -rwxrwxr-x 1 zxl zxl 16000 Sep 25 11:28 mycode* zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ ./mycode hello world!2.2、gcc 编译选项
格式: gcc [ 选项 ] 要编译的文件 [ 选项 ] [ 目标文件 ]
预处理(进行宏替换)
- 预处理功能主要包括宏定义、文件包含、条件编译、去注释等
- 预处理指令是以 # 号开头的代码行
- 选项 “ -E ”,该选项的作用是让 gcc 在预处理结束后停止编译过程
- 选项 “ -o ” 是指目标文件,“ .i ” 文件为已经预处理过的 C 原始程序
例如:
代码:
// code.c// 头文件#include<stdio.h>// 宏替换#defineM100#defineNintmain(){printf("hello world!, %d\n", M);printf("hello world!\n");// 注释//printf("hello world!\n");//printf("hello world!\n");//printf("hello world!\n");printf("hello world!\n");// 条件编译#ifdefNprintf("hello N\n");#elseprintf("hello no N\n");#endifreturn0;}编译(预处理):
-rw-rw-r-- 1 zxl zxl 368 Sep 25 11:47 code.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -E code.c -o code.i -rw-rw-r-- 1 zxl zxl 18053 Sep 25 11:48 code.i 我们的 code.i 文件就是我们源文件在编译时预处理结束后就停止编译的文件。
此时我们预处理结束后头文件打开了,同时的宏被替换了,注释被去除的同时条件编译也执行了,所以就会成为上面的效果。
编译(生成汇编)
- 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言
- 用户可以使用 “ -S ” 选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码
编译(生成汇编):
我们可以拿 .c 文件直接进行生成汇编,也可以把已经预处理完成后的文件 .i 进行生成汇编。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -S code.i -o code.s # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -S code.c -o code.s -rw-rw-r-- 1 zxl zxl 924 Sep 25 13:48 code.s 此时我们的代码就会被加工成汇编语言,同理 -S 只会把代码完成到生成汇编这一步,生成汇编后就停止下来了。
汇编(生成机器可识别代码)
- 汇编阶段是把编译阶段生成的 “ .s ” 文件转成目标文件
- 读者在此可使用选项 “ -c ” 就可看到汇编代码已转化为 “ .o ” 的⼆进制目标代码
编译(生成机器可识别代码):
同理,我们可以拿汇编之前生成的 .c、.i、.s 文件进行汇编。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code.c -o code.o # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code.i -o code.o # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code.s -o code.o -rw-rw-r-- 1 zxl zxl 1752 Sep 25 13:54 code.o 此时我们的代码就会被加工成机器可识别代码,同理 -c 只会把代码完成到这一步就停止下来了。
此时我们的文件就已经变成二进制文件了。但是我们还没有办法运行,这是因为我们 .o 文件只不过是把我写的代码变成二进制文件了。但是我的代码中用到的各种头文件和各种库函数等,都没有编译进去,所以我们还得有一个连接阶段。
连接(生成可执行文件或库文件)
- 在成功编译之后,就进入了链接阶段
编译(生成可执行文件或库文件):
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.c -o code # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.i -o code # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.s -o code # 或者 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.o -o code -rwxrwxr-x 1 zxl zxl 16000 Sep 25 14:02 code* zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ ./code hello world!, 100 hello world! hello world! hello N 三、g++ 编译
gcc 编译是给我们 C 语言文件进行编译的, g++ 编译可以给我们 C 语言和 C++文件进行编译,它的编译方法和我们 gcc 一样,我们只要把前面的 gcc 变成 g++ 即可,这里就不过多赘述了。
四、静态库和动态库
- 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名⼀般为 “ .a ”。
- 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 “ .so ”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
有了这些库以后,我们想要去实现很多常用的功能,就没有必要从零开始造轮子,会有人把这些功能为我们写好,放到库中供我们使用。这样也统一了我们各功能,使其不会一个功能却有成千上百中实现方式。
Linux 当中,肯定也会提前安装很多动静态库,存在 /usr/lib64 目录文件中。
注意:
一个库的真实名字是它的名字去掉前面的 lib,再去掉 . 后面的后缀,剩下的就是库的真实名字了。
五、动态链接和静态链接
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个 *.c 文件会形成一个 *.o ⽂件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接。
静态链接的缺点很明显:
- 浪费空间:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标⽂件都有依赖,如多个程序中都调用了 printf() 函数,则这多个程序中都含有 printf.o,所以同一个目标文件都在内存存在多个副本
- 更新比较困难:因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
静态链接的优点是:
- 在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快
动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。动态链接只需要能够找到库中的对应方法的地址,即可让我们的方法调用起来。
动态链接其实远比静态链接要常用得多。比如我们查看下 code 这个可执行程序依赖的动态库,会发现它就⽤到了⼀个 C 动态链接库。
在 Linux 中可以使用 ldd [可执行文件名] 来查看我们的这个文件依赖什么库以及是什么链接。
-rwxrwxr-x 1 zxl zxl 16000 Sep 25 14:02 code* zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ ldd code linux-vdso.so.1 (0x00007ffdcb7f2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd90923b000)/lib64/ld-linux-x86-64.so.2 (0x00007fd909471000)这里面 libc.so.6 就是 C 的动态库,也叫 C 标准库。它的链接方式就是动态链接。
还可以使用 file [ 可执行文件名 ] 的指令来查看。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ file code code: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=73b57ba77bf13d1b9f3cee616c44215760563964,for GNU/Linux 3.2.0, not stripped 从它的返回的字符串可以看到:
- 64-bit:64位的
- dynamically linked:动态链接
同时我们看动态库的文件大小为 16000。我们来看看如果是静态链接我们的文件大小会发生什么改变以及 file 和 ldd 会变成什么样子。
想要进行静态链接我们得在我们 gcc 命令后面加一个 -static 选项。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.c -o code -static -rwxrwxr-x 1 zxl zxl 900440 Sep 25 16:50 code*我们可以看到我们的文件会变大非常多,将近 100 倍。这只是用了一个库的情况,要是有很多库,那就会非常浪费空间了。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ ldd code not a dynamic executable 此时我们 ldd 后会发现它说我们不是一个动态可执行。
zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ file code code: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=6c381730b960e1b3426a6071c0283120d7bcef6e,for GNU/Linux 3.2.0, not stripped file 后显示 statically linked(静态链接)的 executable(可执行程序)。
注意:
我们之所以能够使用动静态库是因为我们 Linux 早就预装了这些库在我们 /usr/lib64 目录下,如果没有预装库,那就会报错,解决办法就是去网上找到这些库然后去安装,这也就是为什么有的时候我们直接复制别人网上的代码却发现运行不了的原因。
我们说了动静态库的和我们的函数的链接,那我们如何将我们自己写的库和我们的函数链接呢?
其实很简单,让我们的库和我们的函数一起形成一个可执行文件即可。如下我们把 code1.h、code1.c、code2.h、code2.c 和我们 code.c 文件进行链接形成可执行文件。
-rw-rw-r-- 1 zxl zxl 0 Sep 25 17:16 code1.h -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:16 code1.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:16 code2.h -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:16 code2.c -rw-rw-r-- 1 zxl zxl 368 Sep 25 11:47 code.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code1.c code2.c code.c -o code -rwxrwxr-x 1 zxl zxl 16000 Sep 25 17:17 code*当然,如果你不想让别人看到你的源文件,就比如你把这个代码给室友,让其完成作业,但是你不想让他知道你的代码是怎么实现的,此时你可以把你的 * .c 文件把它编译成 * .o 文件,然后让 * .o 文件和别的文件进行链接,这样就不会被别人知道你的代码是怎么实现的了(*.h 不用加密,因为如果加密了别人就不知道怎么使用你写的函数了)。
-rw-rw-r-- 1 zxl zxl 0 Sep 25 17:27 code1.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:28 code1.h -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:27 code2.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:28 code2.h -rw-rw-r-- 1 zxl zxl 368 Sep 25 11:47 code.c # 不写 -o 已经后面的目标文件,系统会自动生成要编译的文件名加相应的后缀的文件 zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code1.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code2.c zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc -c code.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:27 code1.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:28 code1.h -rw-rw-r-- 1 zxl zxl 936 Sep 25 17:29 code1.o -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:27 code2.c -rw-rw-r-- 1 zxl zxl 0 Sep 25 17:28 code2.h -rw-rw-r-- 1 zxl zxl 936 Sep 25 17:29 code2.o -rw-rw-r-- 1 zxl zxl 368 Sep 25 11:47 code.c -rw-rw-r-- 1 zxl zxl 1752 Sep 25 17:36 code.o zxl@iv-ye423qlwxsqc6ikwbogx:~/lesson1$ gcc code.o code1.o code2.o -o code -rwxrwxr-x 1 zxl zxl 16064 Sep 25 17:37 code*总结
我们动静态库在后面会有一章用来专门讲解,所以大家能够大致了解即可,这里我们能够知道我们写的代码文件能够如何进行编译形成可执行的文件即可。剩下的我们后面会来讲解的,我们下一章节再见。
🎇坚持到这里已经很厉害啦,辛苦啦🎇ʕ • ᴥ • ʔづ♡ど