学习交流加
- 个人qq: 1126137994
- 个人微信: liu1126137994
- 学习交流资源分享qq群: 962535112
1、GCC与gcc
-
GCC (GNU Compiler Collection)
- GNU 编译器集合,包含众多语言的编译器,包括C,C++,Java等等
- GCC多用于嵌入式操作系统的编译,如Linux,VxWorks,Android等等
-
gcc 单指GCC中的C语言编译器
- gcc 多用于内核开发以及少数应用程序开发
2、gcc的幕后工作
想了解更多更详细的关于编译链接深层次内容,请阅读书籍《CSAPP》第7章与《程序员的自我修养》,因为这里我的学习记录只记录结果与常用的几个编译方法。
我们先来看一个简单的程序:
test.c源程序:
#include#include "func.h"int g_global = 0;int g_test = 1;int main(int argc, char *argv[]){ func(); printf("&g_global = %p\n", &g_global); printf("&g_test = %p\n", &g_test); printf("&func = %p\n", &func); printf("&main = %p\n", &main); return 0;}复制代码
func.h头文件:
#includevoid func(){#ifdef TEST printf("TEST = %s\n", TEST);#endif return;}复制代码
在Linux下使用gcc进行编译:
gcc test.c -o test复制代码
然后运行:
./test复制代码
结果如下:
&g_global = 0x804a020&g_test = 0x804a014&func = 0x80483c4&main = 0x80483c9复制代码
很明显,上述程序很简单,大一的新生都知道为什么。但是今天我们不是学习这个程序的,而是想要了解,运行 gcc test.c -o test 这个命令后,是如何一步一步生成可执行文件test的。
实际上,上述C程序从源文件到二进制可执行文件,有以下四个步骤:
- 预处理 cpp
- C编译 cc
- 汇编 as
- 链接 ld
大概编译一个源程序为二进制文件的过程如下图所示:
当然,上面没有列出链接器,在生成file.o后,还需要将file.o与系统的库文件进行链接,生成最终的可执行文件。
从而,我们就知道了,gcc其实内部包含了预处理器,编译器,汇编器,链接器这四部分。
这四部分这里只是来简单介绍一下(网上一大堆,本文侧重点不在此):
- 预处理器:预处理,将源程序的宏定义与带‘#’的部分展开
- 编译器:将预处理后得到的文件进行第一次编译得到对应的汇编源程序
- 汇编器:将第二步得到的汇编源程序进行第二次编译即汇编,得到二进制文件(可重定位文件)
- 链接器:将可重定位文件与相应的库进行链接生成最终的可执行文件
3、实用的gcc选项
本文的重点来了,上述的内容过于简单,而本节的内容虽然不难,但是并不被大多数人所了解,所以是本文的重点学习记录。
下面将要学习的gcc选项,在工作中具有很强的实用性。
3.1、预处理选项-解决宏错误
gcc -E file.c -o file.i复制代码
实用上述编译选项 -E 可以得到预处理后的文件,有时候我们在程序中定义的宏可能有错误,而这种错误又很难找,此时如果能得预处理后的文件,就可以方便定位错误。
3.2、-S参数-辅助编写汇编程序的好方法
写汇编程序很难,但是如果先写成C语言,再将这个C语言转化成汇编语言,就会很简单。gcc编译工具中,-S选项,可以达到这个目的。比如以下程序: foo.c程序:
#includevoid foo(){ printf("This is foo().\n"); }复制代码
我们使用如下命令进行编译:
gcc -S -O2 foo.c -o foo.s复制代码
将会生成一个foo.c相同作用的汇编程序foo.s,如下:
.file "foo.c" .section .rodata.str1.1,"aMS",@progbits,1.LC0: .string "This is foo().\n" .text .p2align 4,,15.globl foo .type foo, @functionfoo: pushl %ebp movl %esp, %ebp subl $24, %esp movl $.LC0, 4(%esp) movl $1, (%esp) call __printf_chk leave ret .size foo, .-foo .ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5" .section .note.GNU-stack,"",@progbits复制代码
使用-S 参数时,我们可以根据需要使用-O优化选项。从foo.s的内容可以看出,"This is foo().\n" 这个字符串是放在.rodata段的。看来获取C程序对应的汇编代码,对C语言实现方面的细节,也有所帮助。
3.3、获取系统头文件路径
gcc -v file.c 复制代码
获取file.c使用的系统头文件的位置
3.4、产生映射文件
如果我们想要知道程序中各个符号的内存布局的信息,可以使用如下命令:
gcc -Wl,-Map=file.map file.c -o file复制代码
3.5、通过选项定义宏
有时候程序中需要的某一个常量会依赖工作环境的不同而改变,这个时候,我们可以将这个常量定义为宏,但是这样,我们还是需要每次都在源程序中将宏的值改变,这也很麻烦,此时就可以利用编译选项 -D,在编译的命令行进行宏定义。
还有就是程序中或许会存在下属这样的代码: test.c程序:
#include#include "func.h"int g_global = 0;int g_test = 1;int main(int argc, char *argv[]){ func(); printf("&g_global = %p\n", &g_global); printf("&g_test = %p\n", &g_test); printf("&func = %p\n", &func); printf("&main = %p\n", &main); return 0;}复制代码
test.h头文件:
#includevoid func(){#ifdef TEST printf("TEST = %s\n", TEST);#endif return;}复制代码
在头文件中,有一处定义 # ifdef TEST ....
很明显,上面的两个文件,都没有定义这个TEST,所以程序运行结果如下:
&g_global = 0x804a020 &g_test = 0x804a014 &func = 0x80483c4 &main = 0x80483c9复制代码
但是可能在某个场合,又必须要使用TEST定义,那么此时,我们肯定不愿意在程序中改来改去,此时就利用编译器的 -D选项,来定义这个TEST。如下编译命令:
gcc -D'TEST="test" ' test.c -o test复制代码
运行程序后,结果如下:
TEST = test&g_global = 0x804a020&g_test = 0x804a014&func = 0x80483c4&main = 0x80483e1复制代码
3.6、生成依赖关系
大多数人应该知道make,如果不知道也没有关系。 在makefile中,make需要通过依赖关系来决定,每次构建时哪些文件需要重新编译。使用gcc的-M选项,可以得到make所需要的源文件的依赖关系。-MM选项可以让gcc生成不包含系统文件的依赖关系。
比如有如下源文件: main.c源文件(main.h与foo.c的内容是什么都行)
#include#include "main.h"#include "foo.c"int main(){ printf("Hello world!\n"); return 0; }复制代码
对其进行如下编译
gcc -M main.c复制代码
将得到如下输出:
可以看到,这句是make所需要的main.c的依赖关系。
如果使用如下命令的话:
gcc -MM main.c复制代码
将得到如下输出:
结果显而易见!!!3.7、指定链接库
当一个可执行程序的生成,需要使用其他库时,需要在链接时加以指定。这就需要用到gcc 的-l与-L选项。
假设一个程序叫做main.c,它编译成可执行程序不光需要系统的标准库,还需要一个库:libfoo.a 且这个libfoo.a与main.c在同一个目录,那么在编译main.c时,需要以下命令:
gcc -o main -L. main.c -lfoo复制代码
注意:
- -L告诉gcc编译器,当前可以从哪个目录查找库文件,此处-L后面跟了一个**点‘.’**表示当前目录。
- -l选项,告诉编译器需要连接的库名。这里并没有写“lib”前缀和“.a后缀”。-lfoo就是代表指定libfoo.a库参与链接。
更加详细的内容参考《程序员的自我修养》
4、总结
今天学习了gcc的简单概念,与gcc的常用的参数选项。