一、GCC简介
GCC即GNU Compiler Collection,原本只是针对C语言的编译工具,现在已经变成了一个工具集,包含了C、C++、JAVA等语言的集合体。
管理和维护:由GNU项目负责。
二、GCC对C、C++的编译流程
(1) 预处理(Preprocessing)
--宏替换;
--删除注释;
--处理预处理指令,如#include,#ifdef,头文件都被展开,拼接所有头文件到一个文件中。
(2) 编译(Compilation)
--词法分析,识别单词,确认词类;比如 int i;知道 int 是一个类型,i是一个关键字以及判断i的名字是否合法
--语法分析,识别短语和句型的语法属性;
--语义分析,确认单词、短语和句型的语义特征;
--代码优化,修辞、文本编辑;
--代码生成,生成译文(.s文件,统一代码在不同系统中的.s文件不一致)。
(3)汇编(Assemby)
--把汇编语言代码翻译成目标机器指令(生成目标文件,.o文件)。
编译器把一个cpp编译为目标文件的时候,除了要在目标文件里写入cpp里包含的数据和代码,还要至少提供3个表:未解决符号表,导出符号表和地址重定向表。
未解决符号表,提供了所有在该编译单元里引用但是定义并不在本编译单元里的符号及其出现的地址。
导出符号表,提供了本编译单元具有定义,并且愿意提供给其他编译单元使用的符号及其地址。
地址重定向表,提供了本编译单元所有对自身地址的引用的记录。
(4) 链接(Linking)
--将所有.o文件及引用到的动态链接库文件中的函数链接到一起,生成可执行文件(.out文件)。
三、GCC的基本使用(以下选项在g++中同样试用,一般c程序就用gcc编译,c++程序就用g++编译)
基本使用格式 : $ gcc [选项] <文件名>
(1) -o 设置gcc处理结果的文件名为<文件名>
如 gcc main.c -o main ,即为将编译结果的文件名设置为main,如果不使用-o命令,则编译结果是默认的文件名(假如被处理的源文件为source.suffix,若省略了-o选项,则生成的可执行文件默认为a.out,目标文件默认名为source.o,汇编文件默认为source.s)。
例如对于这个main.c
-- 执行 gcc main.c (输出默认文件名)
-- 执行 gcc main.c -o main (指定gcc处理结果的文件名为main)
(2) -c 只编译,不链接
执行 gcc -o main.c (结果为.o文件)
一般对于包含对个.c文件的大工程,首先把每个.c文件编译成独立的.o文件,然后把所有.o文件进行链接生成可执行文件。
(3) -E 预编译 (将所有的头文件和宏都拼接到一个.c文件中)
执行 gcc -E main.c (可见在main()函数之前,所有的头文件和宏定义都被包含进来(先把头文件copy过来,然后对宏进行替换))
也就是说对于一个程序,预编译会把所有的头文件和宏定义都拼接到一个文件中,因为其实一个程序就算其包含了很多子文,但是最终都是编译一个文件。
(4) -S 只编译不汇编
执行 gcc -S main.c (生成.s文件,同意程序在不同体系下的计算机中生成的汇编文件是不一致的)
(5) -g 生成调试信息(debug版本)
执行 gcc -g main.c -o main_d ,生成带调试信息的版本,并令生成的文件名为“main_d”。可见加了-g的版本比普通版本所占空间更大,因为debug版本加入了调试信息,会把代码编进去,后面可以使用GDB工具进行调试跟进。
【多目录编译】
(6) -I 添加头文件路径(多文件目录下)
例如有如下目录:
--person
--person/person.h
--person/person.cpp
--test
--test/main.cpp
main.cpp如下
person.h如下
person.cpp如下
编译过程如下:
(a). 执行 g++ -c person.cpp ,首先单独编译person.cpp生成目标文件person.o 。
(b). 执行 g++ main.cpp ../person/person.o -o main -I ../person,编译person.o和main.cpp,-I添加头文件目录。
【静态编译】
(7) -static 静态编译(若指定为静态编译,则会把所有引用到的系统文件都指定成静态库,全部编译到这个可执行文件中)
使用ldd可以查看程序所引用到的动态库。
执行 ldd main ,可见一个简单的程序都引用了很多库。
执行 g++ main.cpp ../person/person.o -o main_static -I ../person -static ,将程序进行静态编译(使用个g++是因为gcc编译c++找不到库文件)。
然后再使用ldd main_static查看调用的动态库,可以发现它不调用任何动态库。
而且可执行文件的大小变得非常大。
注:大部分情况下不建议使用静态方式进行编译,因为其最大的问题就是编译时间太长,稍微大点的项目编译要几分钟,但是它在其他环境下运行比较简单,不需要配置环境依赖。
【动态编译】
(8) -fpic -shared 前者指定函数和代码位置不相关,设置之后可以使用-shared将其设置为动态链接库(动态链接库在linux中的命名规则为 lib+name+.so,引用的时候只要使用name就行了)。
执行 gcc person.cpp -fpic -shared -o libperson.so ,将person.cpp编译成动态链接库 libperson.so。
(9) -L -l(小写的L) 前者指定动态链接库的所在目录,后者指定需要链接的动态链接库(不加lib 和.so)
执行 g++ main.cpp -o main -I../person -L../person -lperson ,在编译main时使用-I(大写的i)指定头文件目录,使用-L指定董涛链接库目录,使用-lperson指定需要在编译时链接libperson.so这个动态链接库文件。
注意:执行通过动态链接而编译生成的可执行文件main时,需要指定动态链接库文件libperson.so的路径,否则运行数失败。
解决方法有两种:
(a) 将需要链接的动态链接库文件libperson.so拷贝到系统的path路径下。
(b) 写执行脚本export LD_LIBRARY_PATH=../person,添加临时路径。
使用ldd命令查看main依赖,可见确实在../person目录下找到的。