Fork me on GitHub

gcc编译链接以及动态库静态库

  gcc 是 “GNU Compiler Collection” 的缩写,最早是由 RMS 编写的一款 C 编译器,是GUN项目的基石之一;现在GCC已经成为了能编译C、C++、Ada、Object C和Java等语言的GNU编译器家族,同时还可执行跨硬件平台的交叉编译工作。

  gcc [-选项1-][-选项2-] …. [-选项n-] <源文件名>

  命令名、选项和源文件名之间使用空格分隔,一行命令中可以有多个选项,也可以只有一个选项。文件名可以包含文件的绝对路径,也可以实用相对路径。如果文件名不包含路径,那么源文件被视为在于工作目录中。如果命令中不包含输出的可执行文件名称,那么默认情况下将在工作目录中生成后缀为.out的可执行文件。

  GCC编译选项

编译选项 说明
-c 只进行预处理、编译和汇编,生成.o文件
-S 只进行预处理和编译,生成.s文件
-E 生成预处理文件
-C 预处理时不删除注释信息,常与-E同时使用
-o 指定目标名称,常与-c、-S同时使用,默认是.out
-include file 插入一个文件,功能等同源代码中的#include
-Dmacro[=defval] 定义一个宏,功能等同源代码中的#define macro[defval]
-Umaacro 取消一个宏,功能等同源代码中的#undefine macro
-Idir 优先在选项后的目录中查找包含的头文件
-Iname 指定动态库名
-Ldir 指定编译搜索库的路径
-g 编译器编译时加入debug信息
-pg 编译器编译时加入信息给gprof
-share 使用动态库
-fPIC 编译为位置独立的代码
-static 禁止使用动态库

  GCC编译C源码有四个步骤:预处理—> 编译 —> 汇编 —> 链接,如下图

  1. 预处理(Pre-processing)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    [test@hurd]$ vim test.c
    #include <stdio.h>
    int main(void)
    {
    printf("Hello world\n");
    return 0;
    }
    [test@hurd]$ gcc -E test.c -o test.pre.c
    [test@hurd]$ vim test.pre.c
    # 1 "test.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<command-line>" 2
    # 1 "test.c"
    # 1 "/usr/include/stdio.h" 1 3 4
    # 27 "/usr/include/stdio.h" 3 4
    # 1 "/usr/include/features.h" 1 3 4
    # 374 "/usr/include/features.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
    # 385 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
    # 386 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
    # 375 "/usr/include/features.h" 2 3 4
    # 398 "/usr/include/features.h" 3 4
    ......
    ......
    extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    # 943 "/usr/include/stdio.h" 3 4

    # 2 "test.c" 2
    int main(void)
    {
    printf("Hello world\n");
    return 0;
    }
    ###在此阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来
  2. 编译阶段(Compiling)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    ###在此阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言;用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
    [test@hurd]$ gcc -S test.pre.c -o test.s
    [test@hurd]$ vim test.s
    .file "test.pre.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
    movl $0, %eax
    popq %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
    .LFE0:
    .size main, .-main
    .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4"
    .section .note.GNU-stack,"",@progbits
  3. 汇编阶段(Assembling)

    1
    2
    3
    4
    ###此阶段是把编译阶段生成的".s"文件转成二进制目标代码
    [test@hurd]$ gcc -c test.s -o test.o
    [test@hurd]$ ls
    test.c test.pre.c test.o test.s
  4. 连接阶段(Link)

    1
    2
    3
    4
    ###此阶段,将编译输出文件"test.o"链接成最终可执行文件"test"
    [test@hurd]$ gcc test.o -o test
    [test@hurd]$ ./test
    Hello world

      在这个小程序中并没有定义”printf”函数的实现,且在预编译中包含的头文件”stdio.h”中也只有该函数的声明,而没有定义函数的实现;那么,是在哪里实现”printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索系统路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” ,而这也就是链接的作用。
      用ldd命令查看动态库加载情况:

    1
    2
    3
    4
    [test@hurd]$ ldd test
    linux-vdso.so.1 => (0x00007ffcf3561000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efc1c358000)
    /lib64/ld-linux-x86-64.so.2 (0x000055d59c8fe000)

      函数库一般分为静态库和动态库。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”;动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时加载,以节省系统的开销。动态库一般后缀名为”.so”,libc.so.6就是动态库。gcc在编译时默认使用动态库。

  静态库(.a、.lib)和动态库(.so、.dll);windows上对应的是.lib、.dll;linux上对应的是.a、.so

  Linux静态库命名规范,必须是”lib[your_library_name].a”:lib为前缀,中间是静态库名,扩展名为.a。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
###创建静态库
###将代码文件编译成目标文件.o
[test@hurd]$ gcc -c test.c -o test.o
###通过ar工具将目标文件打包成.a静态库文件
###ar(archive)对一个或者多个目标文件.o进行归档, 形成一个静态库.a文件
###ar crs ------ 把目标文件转换为静态库
###ar -vt libtest.a 查看静态库中有哪些目标文件 (实际上就相当于:objdump -a libtest.a)
###ar -x libtest.a 将静态库中的目标文件取(解压)出来,放在当前目录下
[test@hurd]$ vim foo1.c
void foo1()
{
printf("This is foo1!\n");
}
[test@hurd]$ vim foo2.c
void foo2()
{
printf("This is foo2!\n");
}
[test@hurd]$ vim main.c
#include <stdio.h>

int main(void)
{
foo1();
foo2();
printf("This is main!\n");
return 0;
}
[test@hurd]$ ar crs libfoo.a foo1.o foo2.o
[test@hurd]$ ar -t libfoo.a
foo1.o
foo2.o
[test@hurd]$ gcc main.c libfoo.a -o main
[test@hurd]$ ./main
This is foo1!
This is foo2!
This is main!
###运行结果正常

  Linux动态库命名规则:形式为 libxxx.so,前缀是lib,后缀名为””.so”;对于实际库文件,每个共享库都有个特殊的名字”soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库;在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用;它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
###创建动态库
###-fPIC 创建与地址无关的编译程序(pic,position independent code),为了能够在多个应用程序间共享
###-share 使用动态库
[test@hurd]$ gcc -shared -fPIC -o libfoo.so foo1.c foo2.c
[test@hurd]$ gcc -o main2 main.c -L. -lfoo
###-L 指定在当前路径搜索动态库
###-l 指定动态库foo
[test@hurd]$ ./main2
./main2: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
###发现报错,很正常,因为libfoo.so在当前目录,并不在程序运行时搜索动态库的目录
[test@hurd]$ vim /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
###/etc/ld.so.conf.d/*.conf文件中定义了程序运行搜索动态库的目录
###两种方法解决上述问题,将libfoo.so复制到/lib或/usr/lib下
###另一种方法时将libfoo.so所在的目录添加到/etc/ld.so.conf文件中,并运行ldconfig
[test@hurd]$ vim vim /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
/home/deepcam/freegcc
[test@hurd]$ sudo ldconfig
[test@hurd]$ ./main2
This is foo1!
This is foo2!
This is main!
###程序正常运行
###ldd命令可以查看一个可执行程序依赖的共享库
[test@hurd]$ ldd main2
linux-vdso.so.1 => (0x00007fffa95e5000)
libfoo.so => /home/deepcam/freegcc/libfoo.so (0x00007f5826e6c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5826aa7000)
/lib64/ld-linux-x86-64.so.2 (0x00005654cca81000)
------ 本文结束 ------