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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章