gcc编译选项及动/静态库

本文详细介绍了C++的编译过程,包括预处理、编译、汇编和链接四个步骤,并讲解了g++编译器的常用参数,如-g用于添加调试信息,-O[n]进行代码优化,-l和-L指定库文件,-I指定头文件路径,-std=c++11设定编译标准等。此外,还讨论了静态库和动态库的创建、使用及运行时库文件搜索路径,强调了静态库和动态库在内存占用和更新灵活性上的区别。最后,文章提供了编译实例和库文件管理的实践操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 编译过程

  1. 预处理 (加 -E 选项生成 .i 文件)

    -E 选项指示编译器仅对输入文件进行预处理,预处理文件的拓展名是 .i 。

    g++ -E test.cpp -o test.i
    
  2. 编译 (加 -S 选项生成 .s文件)

    -S 选项告诉编译器在为 C++ 代码产生了汇编语言文件后停止编译,g++ 产生的汇编语言文件的缺省扩展名是 .s 。

    g++ -S test.i -o test.s
    
  3. 汇编 (加 -c 选项生成 .o 文件)

    -c 选项告诉编译器仅把源代码编译为机器语言的目标代码,缺省时编译器建立的目标代码文件有一个 .o 的扩展名。

    g++ -c test.s -o test.o
    
  4. 链接 (bin文件)

    -o 选项来为将产生的可执行文件用指定的文件名。

    g++ test.o -o test
    

2. g++ 编译参数

  1. -g:编译带调试信息的可执行文件

    -g 选项告诉编译器产生能被 GNU 调试器 GDB 使用的调试信息,以调试程序。

    产生带调试信息的可执行文件test:

    g++ -g test.cpp
    
  2. -O[n]:优化源代码

所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。-O 选项告诉 g++ 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉 g++ 产生尽可能小和尽可能快的代码。 如-O2,-O3,-On(n 常为0–3)。

-O:同时减小代码的长度和执行时间,其效果等价于-O1

-O0:表示不做优化

-O1:为默认优化

-O2:除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。

-O3:则包括循环展开和其他一些与处理特性相关的优化工作。

选项将使编译的速度比使用 -O 时慢, 但通常产生的代码执行速度会更快。

使用 -O2优化源代码,并输出可执行文件:

g++ -O2 test.cpp
  1. -l (小写)和 -L 指定库文件名称 & 指定库文件路径

-l 参数(小写)就是用来指定程序要链接的库名称,-l 参数紧接着就是库名

在 /lib 和 /usr/lib 和 /usr/local/lib 里的库直接用 -l 参数就能链接

如链接 glog 库:

g++ -lglog test.cpp

如果库文件没放在上面三个目录里,需要使用 -L 参数(大写)指定库文件所在目录。 -L 参数跟着的是库文件所在的目录名。

如链接mytest库,libmytest.so在/home/crazyang/code目录下

g++ -L/home/crazyang/code -lmytest test.cpp
  1. -I (大写)指定头文件搜索目录

/usr/include 目录一般是不用指定的,gcc 知道去那里找,但如果头文件不在 /usr/icnclude 里我们就要用 -I 参数指定了,比如头文件放在 /myinclude 目录里,那编译命令行就要加上 -I/myinclude 参数了,如果不加你会得到一个 “xxxx.h: No such file or directory” 的错误。-I 参数可以用相对路径,比如头文件在当前目录,可以用 -I. 来指定。

如下头文件在myinclude目录中:

g++ -I/myinclude test.cpp
  1. -Wall 打印警告信息

打印出gcc提供的警告信息

g++ -Wall test.cpp
  1. -w 关闭警告信息

关闭所有警告信息

g++ -w test.cpp
  1. -std=c++11 设置编译标准

    使用C++11标准编译

    g++ -std=c++11 test.cpp
    
  2. -o 指定输出文件名

指定即将产生的文件名

如指定输出可执行文件名为test

g++ test.cpp -o test
  1. -D 定义宏

在使用 gcc/g++ 编译的时候定义宏,常用场景:

-DDEBUG 定义DEBUG宏,可能文件中有DEBUG宏部分的相关信息,用个DDEBUG来选择开启或关闭

DEBUG。

如下代码:

#include <stdio.h>
int main()
{
	#define DEBUG
        printf("DEBUG LOG\n");
    #endif
        printf("in\n");
}
// 1. 在编译的时候,使用gcc -DDEBUG main.cpp
// 2. 就能打印出 DEBUG LOG

注:上面是常用的选项,其他更多详细选项可以使用 man gcc 命令查看 gcc 英文使用手册。也可以查看官方文档

3. 编译实例及库文件

最初的目录结构如下:

.
├── include
│   └── mymath.h
├── main.cpp
└── src
    └── mymath.cpp

3.1 直接编译

最简单得编译,并运行

# 直接编译为可执行文件
g++ main.cpp src/mymath.cpp -Iinclude
# 运行a.out
./a.out

增加参数编译,并运行

# 直接编译为可执行文件,但是附带一些参数
g++ main.cpp src/mymath.cpp -Iinclude -std=c++11 -O2 -Wall -o b.out
# 运行b.out
./b.out

3.2 生成库文件并编译运行

3.2.1 链接静态库生成可执行文件
# 进入src目录下
$ cd src

# 汇编,生成mymath.o文件
g++ mymath.cpp -c -I../include

# 生成静态库libmymath.a
ar rs libmymath.a mymath.o

# 回到上级目录
$ cd ..

# change链接,生成二进制可执行文件:staticmain
g++ main.cpp -Iinclude -Lsrc -lmymath -o staticmain
3.2.2 链接动态库生成可执行文件
# 进入src目录下
$ cd src

# 生成动态库libmymath.so
g++ mymath.cpp -I../include -fPIC -shared -o libmymath.so

# 注:上面这一条命令等价于下面两条命令
# g++ mymath.cpp -I../include -c -fPIC
# g++ -shared -o libmymath.so mymath.o

# 回到上级目录
$ cd ..

# change链接,生成二进制可执行文件:sharemain
g++ main.cpp -Iinclude -lmymath -Lsrc -o sharemain

编译完成后的文件目录如下:

.
├── include
│   └── mymath.h
├── main.cpp
├── sharemain
├── src
│   ├── libmymath.a
│   ├── libmymath.so
│   ├── mymath.cpp
│   └── mymath.o
└── staticmain
3.2.3 运行可执行文件

运行静态库生成的可执行文件:

./staticmain

运行动态库生成的可执行文件:

LD_LIBRARY_PATH=src ./sharemain

运行结果:

$ ./staticmain 
3
$ LD_LIBRARY_PATH=src ./sharemain 
3

4. 静态库和动态库

Linux 下,库文件一般放在 /usr/lib/lib 下。

  • 静态库名字一般为 libxxx.a,其中xxx为该lib名称。

  • 动态库名字一般为 libxxx.so.major.minor,xxx 为该lib的名称,major是主版本号,minor是副版本号。

使用ldd xxx可以查看一个可执行程序依赖的共享库,如下查看上面的 sharemain 可执行文件依赖的库:

$ ldd sharemain 
        linux-vdso.so.1 (0x00007fffd3814000)
        libmymath.so => not found
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1cbb450000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1cbb230000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1cbae30000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1cbaa90000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1cbba00000)

有三种方式可以运行编译的可执行文件,还是用上面的例子说明:

  1. 直接编译多个源文件,将目标代码合成一个.o文件,在上面已经使用过了。
  2. 通过创建静态库 libmymath.a说明,使得 main 函数调用 add 函数时候可调用静态链接库,上面也已经使用过了。
  3. 通过创建动态库 libmymath.so,使得 main 函数调用 add 函数时可调用静态链接库,上面也已经使用过了。

4.1 静态链接库

静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名 ,扩展名为 .a 。例如:我们将创建的静态库名为 mymath,则静态库文件名就是 libmymath.a 。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令 。

ar rs libmymath.a mymath.o

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc/g++ 命令生成目标文件时指明静态库名, gcc 将会从静态库中将公用函数连接到目标文件中。注意, gcc 会在静态库名前加上前缀 lib,然后追加扩展名 .a 得到的静态库文件名来查找静态库文件。

g++ main.cpp -Iinclude -Lsrc -lmymath -o staticmain

现在删除静态库文件 libmymath.a,程序照常正常运行,静态库的公用函数已经链接到目标文件中了。

静态库的缺点:

如果我们同时运行了许多程序,并且它们使用了同一个库函数,这样,在内存中会大量拷贝同一库函数。这样,就会浪费很多珍贵的内存和存储空间。使用了动态链接库的 Linux 就可以避免这个问题。动态链接库和静态链接库在同一个地方,只是后缀有所不同。比如,在一个典型的 Linux 系统,标准的动态链接库是 /usr/lib/libm.so。当一个程序使用动态链接库时,在连接阶段并不把函数代码连接进来,而只是链接函数的一个引用。当最终的函数导入内存开始真正执行时,函数引用被解析,动态链接库的代码才真正导入到内存中。这样,动态链接库的函数就可以被许多程序同时共享,并且只需存储一次就可以了。动态链接库的另一个优点是,它可以独立更新,与调用它的函数毫不影响。

4.2 动态链接库

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文 件扩展名为 .so 。例如:我们将创建的动态库名为 mymath ,则动态库文件名就是 libmymath.so 。用 g++ 来创建动态库:

g++ mymath.cpp -I../include -fPIC -shared -o libmymath.so

PCI 命令行标记告诉 gcc 产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样编译出的 mymath.o 可以被用于建立动态链接库。建立动态链接库只需要用 gcc 的 -shared 标记即可。

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。先运行 gcc 命令生成目标文件,再运行查看结果。

g++ main.cpp -Iinclude -lmymath -Lsrc -o sharemain

使用 -lmymath 标记来告 诉 gcc 驱动程序在连接阶段引用共享函数库 libmymath.so-Lsrc 标记告诉 gcc 函数库可能位于当前目录。否则 GNU 链接器会查找标准系统函数目录,-Iinclude 指定头文件搜索目录。

现在运行程序:

$ ./sharemain
./sharemain: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory

错误提示,找不到动态库文件 libmymymath.so 。程序在运行时,会在 /usr/lib 和 /lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。明明已经将库的头文件所在目录通过 -Iinclude进来了,库所在文件通过 -L 参数引导,并指定了 -l 的库名,但通过 ldd 命令察看时,找不到你指定链接的so文件。

这时可以使用 LD_LIBRARY_PATH 命令解决这个问题,LD_LIBRARY_PATH这个环境变量指示动态连接器可以装载动态库的路径。当然如果有root权限的话,可以修改 /etc/ld.so.conf 文件来指定动态库的目录。然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。通常这样做就可以解决库无法链接的问题。

加上LD_LIBRARY_PATH后运行程序便能正常运行:

$ LD_LIBRARY_PATH=src ./sharemain 
3

4.3 库链接搜索路径顺序

静态库链接时搜索路径顺序:

  1. ld 会去找 gcc 命令中的参数 -L 选项
  2. 再找 gcc 的环境变量 LIBRARY_PATH
  3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的。

动态链接时、执行时搜索路径顺序:

  1. 编译目标代码时指定的 -L 动态库搜索路径
  2. 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径
  3. 配置文件 /etc/ld.so.conf 中指定的动态库搜索路径
  4. 默认的动态库搜索路径 /lib/usr/lib

4.4 库链接与环境变量

有关环境变量:

LIBRARY_PATH 环境变量:指定程序静态链接库文件搜索路径

LD_LIBRARY_PATH 环境变量:指定程序动态链接库文件搜索路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_peak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值