GCC

gcc 工作流程图

gcc 编译选项 说明
-E 预处理,但不进行编译
-S 编译指定的源文件,但不进行汇编
-c 编译、汇编生成目标代码,但不进行链接
-o file1 file2 或 file2 -o file1 将文件 file2 编译成文件 file1
-I directory 指定头文件的搜索目录
-L 编译的时候指定搜索库的路径
-l 编译的时候指定使用的库
-g 在编译的时候,生成调试信息,该程序可以被调试器调试
-D 在程序编译的时候,指定一个宏
-w 不生成任何警告信息
-Wall 生成所有警告信息
-On n 为 0~3,优化选项,3 级别最高
-fPIC/fpic 生成与位置无关的代码
-shared 生成共享目标文件,通常用在建立共享库时
-std 指定 c 版本,如 -std=c99

预处理

1
2
gcc -E main.cpp # 不会生成 .i 文件
gcc -E main.cpp -o main.i

编译

1
2
gcc -S main.cpp # 生成 main.s
gcc -S main.cpp -o xxx.s

汇编

1
2
gcc -c main.cpp # 生成 main.o
gcc -c main.cpp -o xxx.o

生成可执行文件

1
2
gcc main.cpp
gcc main.cpp -o xxx

  • 静态库,在程序的链接阶段被复制到程序中,gcc 链接时,会把静态库中代码打包到可执行程序中
  • 动态库,在程序运行时由系统动态加载到内存中供程序使用,gcc 链接时,动态库的代码不会被打包到可执行程序中

好处:代码保密,方便部署和分发

静态库

命名规则

  • Linux, libxxx.a
  • Windows, libxxx.lib

生成静态库

  1. gcc -c xxx.c 获得 .o 文件
  2. .o 文件打包,使用 ar 工具(archive)
1
ar rcs libxxx.a xxx.o xxx.o

r 将文件插入备存文件中 c 建立备存文件(库文件) s 索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 编译得到目标代码
$ g++ -c ./src/add.cpp -o ./src/add.o
# 生成库文件
$ ar rcs ./lib/libcal.a ./src/add.o
# 编译得到程序
$ g++ main.cpp -o main -I ./include -L ./lib -l cal

# 目录
.
├── include
│   └── head.h
├── lib
│   └── libcal.a
├── main
├── main.cpp
└── src
├── add.cpp
└── add.o

动态库

命名规则

  • Linux, libxxx.so
  • Windows, libxxx.dll

生成动态库

1
2
3
4
# gcc 得到 .o 文件,得到和位置无关的代码
$ gcc -c -fpic/-fPIC a.c b.c
# gcc 得到动态库
$ gcc -shared a.o b.o -o libcalc.so

按照静态库里的程序目录生成动态库

1
2
3
4
5
6
7
8
9
10
# 编译得到 .o 文件
$ g++ -c -fpic ./src/add.cpp -o ./src/add.o
# 得到动态库
$ g++ -shared ./src/add.o -o ./lib/libcal.so
# 和静态库一样编译
$ g++ main.cpp -o main -I include/ -L lib/ -l cal
# 直接运行会报错
$ ./main

./main: error while loading shared libraries: libcal.so: cannot open shared object file: No such file or directory

为什么动态库加载失败?

ldd 命令检查动态库依赖关系

1
2
3
4
5
6
7
8
9
$ldd main

linux-vdso.so.1 (0x00007ffd1cd11000)
libcal.so => not found
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa93ef83000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa93ed5b000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa93ec74000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa93f1bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa93ec54000)

可以发现没有找到 libcal.so 所在位置

那么如何定位共享库文件呢?

当系统加载可执行代码时,能够知道其所依赖库的名字,但还需要知道绝对路径,此时需要系统的 动态载入器具 来获取该绝对路径,也就是上面 ldd 输出的 /lib64/ld-linux-x86-64.so.2,对于 elf 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的 DT_RPATH 段环境变量 LD_LIBRARY_PATH/etc/ld.so.cache 文件列表/lib/, /usr/lib 目录 找到库文件后将其载入内存

该解决动态库加载失败?

  • 设置 LD_LIBRARY_PATH 环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 1. 设置临时环境变量
    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qwt/CPP/lesson07/lib

    # 2. 设置永久环境变量
    $ vim ~/.bashrc
    # 在 .bashrc 文件中添加环境变量
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qwt/CPP/lesson07/lib
    # 使环境变量生效
    $ source ~/.bashrc

  • 设置 /etc/ld.so.cache 文件列表

    1
    2
    3
    4
    # ld.so.cache 不能直接修改
    $ sudo vim /etc/ld.so.conf
    # 直接将路径粘贴到该文件中,然后更新该文件
    $ sudo ldconfig

  • 直接将动态库文件放在 /lib//usr/lib 目录下(不建议这么做)

优缺点

库小的话用静态库,大的话用动态库

静态库优点:

  • 静态库被打包到应用程序中加载速度快
  • 发布程序无序提供静态库,移植方便

静态库缺点:

  • 消耗系统资源,浪费内存
  • 更新、部署、发布麻烦

动态库优点:

  • 可以实现进程间资源共享(共享库)
  • 更新、部署、发布简单
  • 可以控制何时加载动态库

动态库缺点:

  • 加载速度比静态库慢
  • 发布程序时需要提供依赖的动态库