🧋GCC(GNU Compiler Collection)是由GNU开发的编程语言编译器。GNU编译器套件包括C、C++、 Objective-C、 Fortran、Java、Ada和Go语言前端,也包括了这些语言的库(如libstdc++,libgcj等。),说那么多就是一个编译器。平时写完的程序就是需要编译之后才能运行。
🧋之前讲过程序的预处理,但之前限制于环境问题无法讲得透彻与直观。在 Linux 中学习 gcc 后,我们终于可以清楚地观察到程序转换至可执行文件时的过程了。
🧋我们都知道,程序要尽量四个步骤才能转换成可执行文件,根据 gcc 的不同选项我们可以得到不同阶段下的文件。
预处理
gcc -E -o printf.i printf.c //-o后跟着的始终为要生成的文件
🧋选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程,选项 “-o” 之后总是跟着生成的目标文件,输入这串命令,gcc 会自动将程序编译到相应的阶段,此时的文件后缀为 .i 。
🧋可以看到,注释的部分被删去,并且多出来八百多行的代码,就是源程序文件将头文件展开后的结果。
编译
gcc -S -o printf.s printf.i
🧋带上选项 "-S" , gcc 自动将文件编译到编译环节结束,此时的文件的内容已变成了汇编语言,同时文件的后缀为 .s 。
1 .file "printf.c" 2 .section .rodata 3 .LC0: 4 .string "hello world" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 .LFB0: 10 .cfi_startproc 11 pushq %rbp 12 .cfi_def_cfa_offset 16 13 .cfi_offset 6, -16 14 movq %rsp, %rbp 15 .cfi_def_cfa_register 6 16 movl $.LC0, %edi 17 call puts 18 movl $.LC0, %edi 19 call puts 20 movl $.LC0, %edi 21 call puts 22 movl $.LC0, %edi 23 call puts 24 movl $0, %eax 25 popq %rbp 26 .cfi_def_cfa 7, 8 27 ret 28 .cfi_endproc 29 .LFE0: 30 .size main, .-main 31 .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)" 32 .section .note.GNU-stack,"",@progbits
汇编
gcc -c -o printf.o printf.s
🧋使用 “-c” 选项使文件编译到汇编结束停止,经过汇编后文件已由原来的汇编代码转换成二进制文件了(后缀为 .o ),直接看的话会发现就是一堆乱码。
🧋并且这个文件目前仍无法运行,若强制访问也会被拒绝。
[Alpaca@VM-12-9-centos ~]$ ./printf.o -bash: ./printf.o: Permission denied
链接
gcc -o printf printf.o //从.o文件开始编译 gcc -o printf printf.c //从头开始编译
🧋这一步结束后整个编译环节就算结束了,即从头到尾编译,因此不用带选项就能达到目的效果。生成的文件就可以直接执行了。
[Alpaca@VM-12-9-centos ~]$ ./printf hello world hello world hello world hello world
函数库
🧋在链接的时候我们所用到的函数并不完全是我们自己的,因此需要与库建立联系,从而可以使用库里面的函数。
🧋函数库又分成了动态库与静态库两种,动态库又叫共享库,其提供的能力是被所有人共享的,就像学校外的网吧一样,你给了钱就能上网,但万一有一天网吧倒闭了,所有的学生都不能去那家网吧上网了。
🧋而静态库则是将你所需要的库中的代码拷贝到自己本地的文件中。就像自己的电脑,即便外面的网吧倒闭了,你仍然可以照常上网。
[Alpaca@VM-12-9-centos ~]$ ldd printf linux-vdso.so.1 => (0x00007ffdf3584000) libc.so.6 => /lib64/libc.so.6 (0x00007f1d8a320000) /lib64/ld-linux-x86-64.so.2 (0x00007f1d8a6ee000) [Alpaca@VM-12-9-centos ~]$ file printf printf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=ab832f77ec91a8a75cf4d749ef046741e5abf2bc, not stripped
🧋我们有两种方法来判断这个可执行文件是动态链接的还是静态链接的,第一就是使用 ldd 来查询函数的所属关系,当文件是 lib 开头后缀为 .so 则表明是依赖的是动态库,若后缀为 .a 则说明其依赖的是静态库。第二种方法就是使用 file 命令,可以细致地观察文件的属性,在这里便可以看到该文件是由动态库链接而成的。
gcc -static -o printf-static printf.c
🧋 Linux 下是默认使用动态链接的,但我们可以使用这个命令进行静态链接。之后查询出的文件的数据类型就表明其是由静态链接构成的了。
[Alpaca@VM-12-9-centos ~]$ file printf-static printf-static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=9444d246f27ba1fed466fb, not stripped
🧋但由此我们可以明显地看出二者不同链接方式之间的区别,静态链接生成的文件所占的空间远大于动态链接的,更加地浪费空间。所以正常情况下还是使用动态链接较好。
[Alpaca@VM-12-9-centos ~]$ ll -rwxrwxr-x 1 Alpaca Alpaca 8361 Jan 11 21:54 printf -rwxrwxr-x 1 Alpaca Alpaca 861288 Jan 11 23:45 printf-static
协助记忆
🧋预处理、编译、汇编三个阶段所对应的选项分别是 “E” "S" "c" , 刚好就是键盘左上角那个 “Esc” ,只不过中间的 s 需要改成大写。同时其所对应的文件后缀为 “.i” 、“.s” 、“.o” 就是 “iso” 。只要稍微用一些方法来记忆之间的区别,相信很快就能熟练起来。
好了,这次gcc的介绍就到这里结束了,关注博主共同进步!!