编译过程
广义的代码编译过程,实际上应该细分为:预处理,编译,汇编,链接。
预处理过程,负责头文件展开,宏替换,条件编译的选择,删除注释等工作。gcc –E表示进行预处理,生成.i文件。[预处理器cpp]
编译过程,负载将预处理生成的文件,经过词法分析,语法分析,语义分析及优化后生成汇编文件。gcc –S表示进行编译,将预处理后的文件不转换成汇编语言,生成文件.s。[编译器egcs]
汇编,是将汇编代码转换为机器可执行指令的过程。通过使用gcc –C或者as命令完成,生成.o目标文件。[汇编器as]
链接,负载根据目标文件及所需的库文件产生最终的可执行文件。链接主要解决了模块间的相互引用的问题,分为地址和空间分配,符号解析和重定位几个步骤。[链接器ld]
gcc常用参数
-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c
他将生成.o的obj文件
-S
只激活预处理和编译,就是指把文件编译成为汇编代码。
例子用法
gcc -S hello.c
他将生成.s的汇编代码,你可以用文本编辑器察看
-E
只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面.
例子用法:
gcc -E hello.c > pianoapan.txt
gcc -E hello.c | more
-o
制定目标名称,缺省的时候,gcc 编译出来的文件是a.out
例子用法
gcc -o hello hello.c
静态库与动态库
静态库:这类库的名字一般是libxxx.a。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。可理解为一堆.o文件的打包。
例子
生成目标文件:gcc –c st1.c st2.c
生成静态库:ar –rsv libsrh.a st1.o st2.o
查看静态库包含哪些.o文件:ar -t libsrh.a
定义一个Main函数,且调用st1.c和st2.c中的子函数,
Gcc –o test main.c –L./ -lsrh
生成可执行文件,这里gcc的参数-L是告诉编译器库文件的路径是当前目录,-l是告诉编译器要使用的库的名字叫srh。
动态库:静态库*.a文件的存在主要是为了支持较老的a.out格式的可执行文件而存在的。目前用的最多的要数动态库了。这类库的名字一般是libxxx.M.N.so,相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。
例子
一个头文件my_so_test.h和三个源文件test_a.c、test_b.c和test_c.c,将他们制作成一个名为libtest.so的动态链接库文件。
与静态库相同首先还是目标.c文件,再生成.so文件。
gcc –o test_a.c test_b.c test_c.c
gcc –shared –fPIC libtest.so test_a.o test_b.o test_c.o
但可以一步到位:
gcc –o libtest.so –shared –fPIC test_a.c test_b.c test_c.c
-shared该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
下面介绍动态库使用:(动态链接和动态加载),这里介绍动态链接。
gcc main.c –ltest –L. –o test
sudo cp libtest.so /usr/lib/
生成可执行文件,这里gcc的参数-L是告诉编译器库文件的路径是当前目录,-l是告诉编译器要使用的库的名字叫test。