Linux开发工具的使用(二)
1.Linux编译器gcc/g++使用
gcc是c语言的编译器,g++是c++的编译器
1.1 背景
程序的翻译分为四个阶段:预处理、编译、汇编、链接,那么这些都是具体做了什么?
.obj文件(.o文件)不可被执行,下面操作进行验证
1.2 验证每一个阶段的效果
gcc [选项] 要编译的文件 [选项] [目标文件]
1.2.1 预处理
gcc/g++ 文件名(作用就是形成可执行文件)
[yinhan@VM-12-12-centos _stu]$ ll total 4 -rw-rw-r-- 1 yinhan yinhan 185 Jan 6 19:11 myfile.c [yinhan@VM-12-12-centos _stu]$ gcc myfile.c //gcc + 文件名:生成可执行程序a.out [yinhan@VM-12-12-centos _stu]$ ll total 16 -rwxrwxr-x 1 yinhan yinhan 8360 Jan 6 19:11 a.out -rw-rw-r-- 1 yinhan yinhan 185 Jan 6 19:11 myfile.c [yinhan@VM-12-12-centos _stu]$ cat myfile.c #include <stdio.h> int main() { printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); return 0; } [yinhan@VM-12-12-centos _stu]$ ./a.out hello world hello world hello world hello world hello world [yinhan@VM-12-12-centos _stu]$
-o:制定目标名称, 默认的时候, gcc 编译出来的文件是 a.out==(-o后面永远紧跟着可执行程序的名称)==
gcc/g++ -o 目标文件名名称 源文件名
gcc/g++ 源文件名 -o 目标文件名名称(这两种方式都是可行的)
[yinhan@VM-12-12-centos _stu]$ ll total 4 -rw-rw-r-- 1 yinhan yinhan 185 Jan 6 19:11 myfile.c [yinhan@VM-12-12-centos _stu]$ cat myfile.c #include <stdio.h> int main() { printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); return 0; } [yinhan@VM-12-12-centos _stu]$ gcc -o myfile myfile.c [yinhan@VM-12-12-centos _stu]$ ll total 16 -rwxrwxr-x 1 yinhan yinhan 8360 Jan 6 19:14 myfile -rw-rw-r-- 1 yinhan yinhan 185 Jan 6 19:11 myfile.c [yinhan@VM-12-12-centos _stu]$ ./myfile hello world hello world hello world hello world hello world [yinhan@VM-12-12-centos _stu]$ gcc myfile.c -o _myfile [yinhan@VM-12-12-centos _stu]$ ll total 28 -rwxrwxr-x 1 yinhan yinhan 8360 Jan 6 19:14 myfile -rwxrwxr-x 1 yinhan yinhan 8360 Jan 6 19:14 _myfile -rw-rw-r-- 1 yinhan yinhan 185 Jan 6 19:11 myfile.c [yinhan@VM-12-12-centos _stu]$ ./_myfile hello world hello world hello world hello world hello world [yinhan@VM-12-12-centos _stu]$
gcc/g++ -E 源文件(把预处理后的代码直接显示在显示器上)
gcc/g++ -E 源文件 -o 目标文件名(把预处理的代码放进目标文件中,.i是预处理文件后缀)
[yinhan@VM-12-12-centos _stu]$ ll total 24 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:22 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:24 myfile.i [yinhan@VM-12-12-centos _stu]$ rm myfile.i [yinhan@VM-12-12-centos _stu]$ ll total 4 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:22 myfile.c [yinhan@VM-12-12-centos _stu]$ gcc -E myfile.c -o myfile.i [yinhan@VM-12-12-centos _stu]$
1.2.2 编译
gcc/g++ -S 源文件(默认生成汇编文件,这个是先对其预处理再编译)
[yinhan@VM-12-12-centos _stu]$ clear [yinhan@VM-12-12-centos _stu]$ ll total 24 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i [yinhan@VM-12-12-centos _stu]$ gcc -S myfile.c [yinhan@VM-12-12-centos _stu]$ ll total 28 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:36 myfile.s [yinhan@VM-12-12-centos _stu]$
gcc/g++ -S 源文件 -o 目标文件名(这个是对预处理文件直接编译成汇编文件,.s是汇编文件的后缀)
[yinhan@VM-12-12-centos _stu]$ ll total 24 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i [yinhan@VM-12-12-centos _stu]$ gcc -S myfile.i -o myfile.s [yinhan@VM-12-12-centos _stu]$ ll total 28 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:38 myfile.s [yinhan@VM-12-12-centos _stu]$
1.2.3 汇编
gcc -c 源文件(先对其预处理再编译再汇编)
[yinhan@VM-12-12-centos _stu]$ ll total 28 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$ gcc -c myfile.s [yinhan@VM-12-12-centos _stu]$ ll total 32 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 2040 Jan 6 19:42 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$
gcc -c 源文件 -o 目标文件名(直接对汇编文件进行汇编)
[yinhan@VM-12-12-centos _stu]$ ll total 28 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$ gcc -c myfile.s -o myfile.o [yinhan@VM-12-12-centos _stu]$ ll total 32 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 2040 Jan 6 19:44 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$
验证.o为后缀的文件是不可被执行的:
[yinhan@VM-12-12-centos _stu]$ ll total 32 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 2041 Jan 6 19:45 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$ ./myfile.o //是因为没有执行权限导致的吗? -bash: ./myfile.o: Permission denied [yinhan@VM-12-12-centos _stu]$ chmod u+x myfile.o //对拥有者提供执行权限 [yinhan@VM-12-12-centos _stu]$ ll total 32 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rwxrw-r-- 1 yinhan yinhan 2041 Jan 6 19:45 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$ ./myfile.o -bash: ./myfile.o: cannot execute binary file [yinhan@VM-12-12-centos _stu]$
1.2.4 链接
gcc/g++ .o文件为后缀的文件名
[yinhan@VM-12-12-centos _stu]$ ll total 32 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 2041 Jan 6 19:45 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$ gcc myfile.o [yinhan@VM-12-12-centos _stu]$ ll total 44 -rwxrwxr-x 1 yinhan yinhan 8408 Jan 6 19:47 a.out //生成可执行文件 -rw-rw-r-- 1 yinhan yinhan 520 Jan 6 19:28 myfile.c -rw-rw-r-- 1 yinhan yinhan 17098 Jan 6 19:28 myfile.i -rw-rw-r-- 1 yinhan yinhan 2041 Jan 6 19:45 myfile.o -rw-rw-r-- 1 yinhan yinhan 817 Jan 6 19:42 myfile.s [yinhan@VM-12-12-centos _stu]$
1.2.5 记忆
ESc:每个阶段的选项,对应的也是键盘上的Esc只不过这里的S是大写
.iso:几个阶段的文件后缀
1.3 链接的理解
首先有一个重要的概念是函数库,我们在起初写的printf(“hello world\n”);这个printf库函数是怎么来实现的呢?这个printf库函数并不是我们自己写的,其实:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数printf了。
1.3.1 ldd指令
ldd是list dynamic dependencies的缩写
用法:ldd + 文件名
作用:列出一个程序所需要得动态链接库(动态链接库也可以说是共享链接库,后缀是.so)
[yinhan@VM-12-12-centos _stu]$ which ldd /usr/bin/ldd [yinhan@VM-12-12-centos _stu]$ file /usr/bin/ldd /usr/bin/ldd: Bourne-Again shell script, ASCII text executable
头文件在哪里呢?
这里表明头文件也是放在/usr/inlcude/路径下的文件
链接既然上面说了是和对应的库链接,那么库在哪里呢?
libc.so.6就是库,本质也是文件。
1.3.2 预备
Linux下:静态库:lib(前缀)xxxxx.a(后缀);动态库:lib(前缀)xxxxx.so(后缀)
常识:在安装visual studio2022等IDE或者编译器的时候实际上的工作是帮我们下载安装语言的头文件和库文件。
常识:Linux下的指令都是程序,也都是文件,相当一部分指令都是用C语言写的,既然是程序那么也会使用库,下面图片中就使用到了libc.so.6动态库!怎么证明?
1.3.3 动态库和静态库感性理解到实际理解
动态库感性理解和实际理解结合
从故事讲起来,方便理解:假如你是一个高中生,你这一天的安排就是上午做数学作业和语文作业,下午做完化学作业就休息会去上网吧,晚自习也有课。然后网吧是向学长问的,他说的是在学校南门左拐300米有个暴风网吧。下午了你把化学作业做完了就照着学长给的路线去了暴风网吧,这个点保安老师都不管,然后你就到了暴风网吧,给主管说我想坐在靠窗户旁边的那台电脑,然后玩了两个小时,时间到了然后回到学校,吃了个晚饭继续上晚自习。
为什么要去网吧?因为我自己没有电脑,需要去网吧才有电脑玩游戏。==那么C语言也是一样,为什么需要调用库呢?原因就是没有对应的方法,也没有能力去自己实现,所以就需要库,库中有对应的方法。==参考下图理解程序和库的关系:
上述这一个例子可能不是很明白。这里拿做作业再说,小时候我们可能有过叫别人帮我们写作业的经历,这些事情都给别人完成了,我们自己可以干别的事情,可能我们去学习了其他的感兴趣的课程或者出去看电影或者教室斗地主等等,这样我们的效率提高了。在我们编写下面这样一段程序的时候:
1 #include <stdio.h> 2 void swap(int* pa, int* pb) 3 { 4 int tmp = *pa; 5 *pa = *pb; 6 *pb = tmp; 7 } 8 9 int main() 10 { 11 printf("hello world\n"); 12 int a = 10; 13 int b = 20; 14 printf("a = %d, b = %d\n",a ,b); 15 swap(&a, &b); 16 printf("a = %d, b = %d\n",a ,b); 17 return 0; 18 }
通过上面的例子来看这段代码,我们很清楚的是这个printf库函数并不是我们自己写的,其实系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数printf了。
我是怎么知道有网吧的?学长告诉我的。假如说我有个表弟,他是即将进入到我这所高中,然后他问我哪里有网吧,我说的是在学校南门左拐300米有个暴风网吧,然后他还没有上高中就知道了。这里说明的是学长坐的工作就是链接
。这里学长就相当于链接器,那么什么叫做动态链接呢?这个案例中就是这个暴风网吧的地址你记住了,在你的大脑里,程序怎么链接的呢?实际上上述中printf这个库函数在调用的时候,其实是把这个printf在库中的地址拷贝放在可执行程序中
故事还没有完,我的表弟进入到高中的时候,学校接到了学生举报,然后警察局封闭了暴风网吧。那么学校刚来的高一的学生很多都指望着这个暴风网吧,但是现在不行了。本来以前的高一学生很多去这个网吧,现在的这届高一学生就去不成了,那么按以前的来说,这个网吧是很多高中生去的,一般都是四五个人一起去,那么我想说的是网吧是也可以说是共享网吧。上面我们说的暴风网吧对应的就是动态库,也可以叫做共享库。这个动态库只需要一份就够了,因为是共享的。那么,上述暴风网吧没有了那么动态库就没有了,那么高中学生就上不了网了,也就是所有依赖这个共享库的程序就都不能使用了。如果库被删掉,那么很多依赖于此库的程序就无法运行了,如果库的路径更改了,无法通过/usr/lib/路径来找到动态库,那么依赖库的程序也就无法运行了。
静态库感性理解和实际理解结合
故事还在继续:我上了大学后,我自己高三打暑假工,然后自己买了台电脑,以前是网吧玩,现在可以直接用自己电脑玩了。那么这里自己手上有了电脑就可以不去网吧玩游戏了。 ==这里就是一个静态链接的概念:将库中对应的实现方法直接拷贝自己的程序中,也就是说上述的那段代码如果用静态链接的话,就不用在库中去找对应的printf库函数地址放在程序中,然后通过地址在库中找到对应的方法来完成,而是直接把printf库函数的这个实现方法直接拷贝到程序中,这样来进行链接。==故事还在继续,我上了大学,学校旁边有个黑网吧,然后警察给查封了。但是我也觉得没关系,因为我手中有电脑。换句话说,一旦静态链接成功,即使库没有了,我们不再依赖于库,程序还是正常运行。
总结
库分为动态库和静态库,静态库让编译器对用户程序进行静态链接,动态库让编译器对用户程序进行动态链接。静态库和静态链接:链接的时候,如果是静态链接,找到静态库,拷贝静态库中的所需要的实现功能的代码到自己的可执行程序中。动态库和动态链接:链接的时候,如果是动态链接,找到动态库,拷贝动态库中的所需要的实现功能的代码的地址到自己的可执行程序中。静态链接成功:程序不依赖任何库,可以独立运行。动态链接成功:程序依赖动态库,一旦动态库缺失,程序无法运行。一般的云服务器,默认的只有动态库
优缺点
动态库优点:因为自身拷贝地址,相比静态库节省很大空间(以下有验证)
动态库缺点:程序依赖动态库,一旦动态库缺失,程序无法运行
静态库优点:程序不依赖任何库,可以独立运行
静态库缺点:因为自身拷贝实现功能代码问题,浪费内存/磁盘空间(以下有验证)
安装c语言静态库指令:yum install -y glibc-static(必须root用户)
安装c++语言静态库指令:yum installl -y glibc-static libstdc+±static(必须root用户)