文章目录
- 一、程序翻译
- 二、gcc使用
- 1、-o
- 2、预处理-E
- 3、编译-S
- 4、汇编-c
- 5、链接
- 三、库
- 四、库的部分实际操作
- 五、Linux项目自动化构建工具 make/Makefile
- 1、规则
一、程序翻译
C语言中,写出代码后,编译器会经过四个阶段才会生成可执行文件。
预处理(头文件展开,条件编译,宏替换,去注释)
编译(汇编语言)
汇编(汇编->可重定位目标二进制文件,不可以被执行的, bin.obj,只把我们自己的代码进行翻译,形成二进制目标文件)
链接(将形成的obj文件和库文件合并,形成可执行程序)
回顾一下这个后,我们再来看gcc/g++使用。这两个其实都一样,不过一个是C语言的,一个是C++的。本篇就gcc来写。
二、gcc使用
1、-o
对一个.c文件,直接gcc的话,我们知道会形成一个a.out文件。如果想形成自己命名的一个文件,需要用到gcc -o指令
[zyd@ecs-83301 zzz]$ vim test.c
[zyd@ecs-83301 zzz]$ cat test.c
#include <stdio.h>
int main()
{
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
return 0;
}
[zyd@ecs-83301 zzz]$ ls
test.c
[zyd@ecs-83301 zzz]$ gcc -o myfile test.c
[zyd@ecs-83301 zzz]$ ls
myfile test.c
[zyd@ecs-83301 zzz]$
也有另一种写法
[zyd@ecs-83301 zzz]$ gcc test.c -o myfile1
[zyd@ecs-83301 zzz]$ ls
myfile myfile1 test.c
[zyd@ecs-83301 zzz]$
-o后面跟的是要指定生成的名称
2、预处理-E
预处理(头文件展开,条件编译,宏替换,去注释)
用gcc是把上面四个阶段全部都过去了,如果我只想看预处理阶段的情况呢?我们先改一下test.c文件
1 #include <stdio.h>
2
3 #define PRINT
4 #define PRINT [ID]
5
6 int main()
7 {
8 printf("hello world\n");
9 printf("hello 1\n");
10 //printf("hello world\n");
11 //printf("hello world\n");
12 printf("hello 2\n");
13
14 printf("M: %d\n", M);
15
16 #ifdef PRINT
17 printf("hello PRINT\n");
18 #else
19 printf("hello print\n");
20 #endif
21
22 return 0;
23 }
~
~
这样就包含了注释,条件编译,头文件展开,宏。
保存退出后,若想要看到预处理结果,需要用到-E指令。
-E:从现在开始进行程序的翻译,预处理做完,就停下来。
[zyd@ecs-83301 zzz]$ gcc -E test.c -o file1.i
如果不加后面的-o指令,程序就会把预处理后的全部打印到屏幕上。预处理后的文件后缀应是i。
打开预处理后的文件,文件最后是这样
可以看到注释,宏替换等等都已经做了,条件编译也选择打印PRINT。而前面八百多行则是展开后的头文件。
3、编译-S
编译(汇编语言)
要看编译文件,需要用到-S指令
-S:从现在开始进行程序的翻译,编译做完,就停下来
和-E一样,也可以不同种写法。
现在我们简单些看
打开file.s文件,里面的行数就减少了很多,变成了汇编语言
4、汇编-c
汇编(汇编->可重定位目标二进制文件,不可以被执行的, bin.obj,只把我们自己的代码进行翻译,形成二进制目标文件)
-c:从现在开始进行程序的翻译,汇编做完,就停下来
这时候打开file.o文件会是一堆乱码,因为是二进制文件。这时候.o文件还是不能执行,即便加上有可执行权限也不行。
5、链接
链接(将形成的obj文件和库文件合并,形成可执行程序)。
链接是程序自主帮用户链接,之后就形成可执行文件a.out,也可以自定义名字。这时候直接用gcc即可。
关于程序是否已经链接,我们也可以看到。
此时可以看到链接了哪个库。我们重点看中间那个。
为什么我们能在linux下编写和编译C/C++代码?Linux系统已经默认携带了语言级别的头文件和语言对应的库。要写成一种语言的代码,我们不仅需要编译器支持,还需要库支持。Linux下,头文件一般在usr/include的目录里,而链接的库则是上面图片中libc那一行。接下来写一写库的具体信息。
三、库
库本质也是一种文件。
静态库一般以lib开头,.a为后缀,libxxxxx.a。在windows下,静态库文件后缀一般为.lib。
动态库一般以lib开头,.so为后缀,libxxxxx.so。在windows下,动态库文件后缀一般为.dll。
Linux在识别的时候会去掉前缀lib和后缀a,中间剩余的就是Linux识别的库。
不过还没有解决问题,我们仍然不知道静态库这个东西为什么叫静态。我们一点点了解。
在下载安装vs时,vs会帮我们装上语言的头文件和库文件,或者我们选择装哪个语言的时候,vs就会装上头文件和库文件,这里前面写过,没有这些,我们无法执行代码。
之前所学的linux指令,有一部分是C语言写的,ldd /usr/bin/指令就可以查看他们的库文件
lixc.so.6那一行中,后面的libc.so去掉lib,去掉so后就是c。
现在我们如何看待指令?指令其实就是程序,给这个程序起好名后,我们就可以使用这个指令,指令也可以叫做工具。
了解了这些,我们再看库。
程序员写代码的时候,Linux会连接一个库,用到库里的功能时,程序就会进入库搜索,找到后就返回这个函数的地址,那么代码就能正常运行了,而想要找到库,就需要链接器,也就是程序的链接阶段。在程序员没有写代码的时候,编译器里或者Linux里就已经存在库文件了,而当我们把代码写入内存并执行时,整个过程也就运行起来了。如果库没有了,那么程序也就无法正常运行,因为我们链接不到库了,同时如果这个库所有人都可用,这个库就是一个动态库,也称为共享库。动态库系统里只有一份。
静态库
当你写完一些代码后,链接器将你所需要的库中的代码拷贝到你的程序中,不像动态库,动态库是进库里并返回函数地址,静态库是把需要的代码拷贝了过来。这样即使库没有了,但是我们已经拿到代码了,依然不影响程序。
总结
1、静态库和静态链接:链接的时候,如果是静态链接,就找到静态库,拷贝静态库中的需要的代码到用户的可执行程序中
2、动态库和动态链接:链接的时候,如果是动态链接,就找到动态库,拷贝动态库中的需要的代码的地址到用户的可执行程序中相应的位置。
3、静态链接成功:用户程序可以不依赖任何库而独立运行
4、动态链接成功:用户程序一直依赖动态库,一旦库缺失,程序就异常。
5、静态库可能会比较浪费空间。
6、动态库因为可共享的特性。所以真正的实现都在库中,程序内部只有地址,比较节省空间。
7、Linux默认使用动态库
四、库的部分实际操作
假设我们已经创建了一个文件test.c,编译它,命名为myfile-static,后面的-static则是告诉系统强制使用静态库。
gcc test.c -o myfile-static -static
ll一下会发现,链接静态库的执行文件比动态库大了相当多,而test.c只是一点代码而已。
ldd myfile-static
file myfile-static
我们可以看到信息已经变成了静态库的
五、Linux项目自动化构建工具 make/Makefile
在写gcc的时候,顺序可能会记错,gcc可能会用错,为了能够更方便编译,编写程序,Linux有个工具,就是make/Makefile。
make是一个命令,Makefile是一个文件。
先创建一个Makefile文件。
1、规则
Makefile是一个围绕依赖关系和依赖方法构建的一个自动化编译的工具。
依赖关系
进入到Makefile文件里。
头一行写:
file:test.c
file是要生成的可执行文件的名字,test.c是源代码文件。这里的关系就是依赖关系,file依赖test.c。
依赖方法
有了依赖,也要有依赖的方式。上一行按下空格后,来到下一行,再按Tab键,再输入
gcc -o file test.c
实际上最终的文件file应当依赖的是.o文件,因为汇编之后才能生成可执行文件;而汇编后的文件又是依赖于编译后的文件.s的,编译又依赖于预处理。细究起来确实如此,不过写的时候直接写依赖于test.c文件即可。
1 file:test.o
2 gcc -o file test.o
3 test.o:test.s
4 gcc -c -o test.o test.s
5 test.s:test.i
6 gcc -S -o test.s test.i
7 test.i:test.c
8 gcc -E -o test.i test.c
9
清理代码
上面那一行代码
file:test.c
冒号左边是目标文件,右边是依赖文件。在依赖关系中,目标文件对应的依赖文件列表可以为空,依赖文件列表是指依赖文件可以有多个。现在写上clean:,后面列表为空,依然可以执行。在依赖方法里,我们用了gcc指令,其他指令一样也可以使用,所以下一行按Tab键后,用上rm指令,就可以删除文件了。
4 clean:
5 rm -f file
在外部删除时,要带上clean,再次make就又可以创建一次file文件了。但为什么要带上一个clean?Makefile在执行文件时,会默认从上到下执行第一个依赖方法,所以我们要执行clean后面的指令就需要带上clean,clean也是一个目标文件,虽然后面的列表为空,但程序仍然正常看待它,可以执行后面的指令。file目标文件在第一个,所以可以省略,调用时也可以make file。
一般在写清理时,会这样写:
4 .PHONY:clean
5 clean:
6 rm -f file
.PHONY是一个关键字,意为总是被执行。file没有总是被执行,所以当你多次make时程序就不会多次生成,并提醒用户已经生成了。
那我们现在也给file加上关键字:
1 .PHONY:file
2 file:test.c
3 gcc -o file test.c
4
5 .PHONY:clean
6 clean:
7 rm -f file
虽然可以多次执行,但最后文件只有一个。
加上这个关键字后,目标文件其实相当于一个伪目标。
在没加关键字之前,系统会提醒你这是最新的可执行文件了,系统不会再去编译,但是系统如何知道最新?假如我们对test.c做一下改变,然后保存退出,再去make,会发现可以编译了,再make又不能编了。对于系统来说,系统会查看源代码和可执行文件的最新时间,只要可执行文件的最新修改时间晚于源代码,就说明这是最新的文件了,就会提醒一下操作者。
针对这点,我们可以欺骗一下系统,touch一次存在过的文件会刷新它的时间,这样就可以继续编译了。
结束。