首页 > 代码库 > GCC 编译过程和链接库
GCC 编译过程和链接库
GCC 加工程序的过程
在Linux下进行C语言编程,必然要采用GNU GCC来编译C源代码生成可执行程序。
一、GCC使用
Gcc指令的一般格式为:Gcc [选项] 要编译的文件 [选项] [目标文件]
其中,目标文件可缺省,Gcc默认生成可执行的文件名为:a.out
我们来看一下经典入门程序"Hello World!"
# vi main.c
#include <stdio.h>
void main ()
{
printf("hello world!\n");
}
用gcc编译成执行程序。
#gcc main.c
该命令将main .c直接生成最终二进制可执行程序a.out
这条命令隐含执行了(1)预处理、(2)汇编、(3)编译并(4)链接形成最终的二进制可执行程序。这里未指定输出文件,默认输出为a.out。
如果要指定最终二进制可执行程序名,那么用-o选项来指定名称。比如需要生成执行程序main ,那么
#gcc main.c -o main
二、GCC的执行过程
从上面我们知道GCC编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。
GCC编译C源码有四个步骤:
预处理-----> 编译 ----> 汇编 ----> 链接
1.预处理,生成.i的文件[预处理器cpp]
2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs]
3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
4.连接目标代码,生成可执行程序[链接器ld]
现在我们就用GCC的命令选项来逐个剖析GCC过程。
1)预处理(Pre-processing)
在该阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来,用户可以使用gcc的选项”-E”进行查看。
用法:#gcc -E main.c -o main.i
作用:将main .c预处理输出main .i文件。
[root]# gcc -E main.c -o main.i
[root]# ls
main.c main.i
[root]# cat main.i
# 906 "/usr/include/stdio.h" 3 4
# 936 "/usr/include/stdio.h" 3 4
# 2 "main.c" 2
main()
{
printf ("Hello world\n");
}
2)编译阶段(Compiling)
第二步进行的是编译阶段,在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
选项 -S
用法:# gcc –S main.i –o main.s
作用:将预处理输出文件main .i汇编成main .s文件。
[root]# gcc –S main.i –o main.s
[root@richard hello-gcc]# ls
main.c main.i main.s
如下为main.s汇编代码
[root@richard hello-gcc]# cat main.s
.file "main.c"
.section .rodata
.LC0:
.string "Hello world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbits
3)汇编阶段(Assembling)
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码.
选项 -c
用法:# gcc -c main.s -o main.o
作用:将汇编输出文件main.s编译输出main.o文件。
[root]# gcc -c main.s -o main.o
[root]# ls
main.c main.i main.o main.s
4)链接阶段(Link)
在成功编译之后,就进入了链接阶段。
无选项链接
用法:# gcc main.o –o main
作用:将编译输出文件main .o链接成最终可执行文件 main
[root]# gcc main.o –o main
[root]# ls
main.c main main.i main.o main.s
运行该可执行文件,出现正确的结果如下。
[root@localhost Gcc]# ./main
Hello World!
在这里涉及到一个重要的概念:函数库
在这个程序中并没有定义”printf”的函数实现,且在预编译中包含进的”stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现”printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” 了,而这也就是链接的作用。
可以用ldd命令查看动态库加载情况:
[root]# ldd main
linux-vdso.so.1 => (0x00007fffb9eee000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f452c209000)
/lib64/ld-linux-x86-64.so.2 (0x00007f452c5b8000)
函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。
静态链接库和动态链接库
静态链接:
链接是在编译器完成的,所有相关对象在编译的时候被整合到一个可执行文件。若程序要用到的函数很多的话,编译的可执行文件会很大。但是编译好的可执行文件可以独立运行,不需要依赖外部。
动态链接:
与前面相对应,在程序运行的时候需要相应的函数的时候才会载入相应的动态库。
动态链接的优点:
除了静态链接库所有的模块化和代码复用外,动态链接库还有如下优点。
可以实现进程之间的库共享:
当多个进程共享一个库时(如stl库和一些系统库是基本上大多数程序都用的),动态链接方式可以只在内存中保留一份副本,
节约内 存。
升级变得简单:
用户只需要升级动态链接库,而无需重新编译链接其他原有的代码就可以完成 整个程序的升级(很多Windows的补丁就是这种方式发布的)。
可以动态载入:
当软件比较大的时候,可以根据需要动态载入/卸载相应的链接库,而无需像静态链接的方式那样一次性全部载入。
这里先解释一下编译的选项:
-static 使用静态链接库编译
-shared 编译成动态链接库
-Lpath 指定查找链接库的路径,-L.中的-L选项表示指定路径,后面的 . 表示路径是当前目录
-lxxx xxx是链接库的名称,例如libstack.so中的stack
静态链接库的创建(linux中静态链接库名必须是libxxx.a)
#gcc -c stack.c
#ar -cqs libstack.a stack.o
使用静态链接库编译程序
#gcc main.c -o ss -static -L. -lstack
若编译的静态链接库不是标准的libxxx.a形式,则可以这样
#gcc main.c -o ss -static xxx.a
(xxx.a必须在当前目录或指定绝对路径,注意若编译后的静态库名为xxx.a,即使手动修改为标准的libxxx.a形式,一样不能用 -Lpath -lxxx的方式,要这样使用则必须重新编译成标准名称的静态库,即libxxx.a形式)
动态链接库的创建(linux中动态链接库名必须是libxxx.so)
#gcc -fPIC -shared stack.c -o libstack.so
使用动态链接库编译程序
#gcc main.c -L. -lstack -o ss
运行一下
#./ss
这时会出错,因为动态链接库没有在系统默认的指定路径,找不到。
让可执行程序找到动态链接库的方法:
1、设置LD_LIBRARY_PATH环境变量,把动态库所在的路径加到这个环境变量中
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/YOUR/PATH
2、把动态链接库文件拷贝到/lib或/usr/lib
3、修改/etc/ld.so.conf文件,把自己的路径作为单独一行写在末尾, 不过不建议直接改这个文件,
因为这个文件包含了/etc/ld.so.conf.d/*.conf,因此只需在/etc/ld.so.conf.d/目录下新建一个xxx.conf,里面写上自己的 动态库的路径即可
查看可执行文件依赖的动态库命令ldd:
#ldd ss ==>查看可执行文件ss依赖的动态库
我的QQ空间原文:http://user.qzone.qq.com/1475032202/blog/1418879205
本文出自 “小鱼” 博客,请务必保留此出处http://poollooq.blog.51cto.com/8663272/1591339
GCC 编译过程和链接库