程序的编译过程
程序的编译过程是将源代码转换为可执行文件的一系列步骤。这个过程通常包括预处理、编译、汇编和链接等阶段
1. 预处理(Preprocessing)
- 预处理器(
cpp
)处理源代码文件中的预处理指令,如#include
和#define
。 - 它展开宏定义,包含头文件,并删除注释。
- 输出是经过预处理的源代码,通常是一个
.i
文件。
2. 编译(Compilation)
- 编译器(如
gcc
或clang
)将预处理后的源代码转换为汇编语言。 - 编译器进行语法检查、语义分析和优化。
- 输出是汇编代码,通常是一个
.s
文件。
3. 汇编(Assembly)
- 汇编器(如
as
)将汇编语言转换为机器代码。 - 它将汇编指令转换为二进制形式的指令。
- 输出是目标文件,通常是一个
.o
文件。
4. 链接(Linking)
- 链接器(如
ld
)将一个或多个目标文件与库文件链接在一起,生成可执行文件。 - 链接器解析外部引用,将库中的代码和数据合并到最终的可执行文件中。
- 输出是可执行文件,通常没有扩展名或有特定平台的扩展名(如Linux下的
.out
)。
5. 库的创建(可选步骤)
- 编译过程中可能会创建静态库(
.a
)或动态库(.so
)。 - 静态库在链接时被复制到可执行文件中。
- 动态库在程序运行时被加载。
库的概念
库(Library)是计算机编程中一个重要的概念,它指的是一组预先编写好的代码,这些代码可以是函数、类、变量等,用于实现特定的功能或服务。库的使用可以提高开发效率,促进代码复用,降低编程难度,并帮助开发者避免重复发明轮子
在Linux系统中,库(Library)主要分为两种类型:静态库和动态库(共享库)
文件扩展名:
-
静态库(Static Library, .a 或 .lib):
- 在Linux系统中,静态库是一种编译时链接的库,它们在程序编译阶段被整合到最终生成的可执行文件中
-
特点:
- 整合性:静态库在编译时被整合到可执行文件中,因此可执行文件包含了所有需要的库代码。
- 大小:由于静态库的代码被整合到可执行文件中,这可能导致可执行文件的体积增大。
- 性能:静态链接的程序在运行时不需要加载额外的库文件,这可能带来轻微的性能提升。
- 兼容性:静态库可以提高程序的兼容性,因为所有依赖都包含在可执行文件中,不依赖于系统上的库版本。
- 静态库通常具有
.a
文件扩展名。
优点:
- 兼容性好:不依赖于外部库文件,适合在不同环境中运行。
- 性能:由于所有依赖都包含在内,可能减少运行时的加载时间
缺点:
- 可执行文件体积大:包含所有库代码,导致文件膨胀。
- 更新不便:库更新需要重新编译所有依赖程序
使用场景:
- 静态库:适合嵌入式系统、不需要频繁更新的应用程序,或者在没有网络连接的环境中分发程序。
创建静态库:
文件扩展名:
优点:
缺点:
-
编译源代码为对象文件:
-
首先,你需要将源代码编译成对象文件(通常是
.o
文件)。这可以通过编译器(如gcc)完成,使用-c
标志来指示编译器只进行编译,不进行链接。gcc -c source_file.c -o source_file.o
-
创建静态库: 使用
ar
命令来创建静态库。ar
是归档器,用于创建、修改或从静态库中提取文件。ar rcs liblibrary_name.a object_file1.o object_file2.o ...
r
:将文件添加到库中,如果库不存在则创建它。c
:创建库时不输出控制信息。s
:创建索引,这可以加快从库中提取文件的速度。
例如,如果你有两个对象文件
file1.o
和file2.o
,你可以这样创建库:ar rcs libmylib.a file1.o file2.o
-
管理静态库:
- 查看库详细内容:使用ar -v 可以列出库中所有对象文件详细信息
-
ar -v libmylib.a
- 查看库内容:使用
ar -t
可以列出库中的所有对象文件。ar -t libmylib.a
- 从库中提取文件:使用
ar -x
可以提取库中的文件。ar -x libmylib.a file1.o
- 删除库中的文件:使用
ar -d
可以删除库中的文件。ar -d libmylib.a file1.o
-
使用静态库:
-
我们在使用静态库进行编译链接时,需要指定头文件的所在路径,库文件的所在路径以及所要调用的库名称
-
-I(大写i):指定头文件所在路径。
-
-L:指定库文件所在路径。
-
-l(小写l):指明需要链接库文件路径下的哪一个库
gcc -o my_program my_program.c -L/path/to/library -lmylib
这里
-L
指定库文件的搜索路径,-l
指定要链接的库(不包括前缀lib
和后缀.a
) -
链接器标志:可以使用
-static
标志来强制链接器链接静态库,忽略对动态库的链接请求。 -
gcc -static -o my_program my_program.c /path/to/libmylib.a
-
动态库(Dynamic Link Library, .so 或 .dll):
-
Linux下的动态库(Dynamic Libraries)提供了一种在程序运行时才加载库的方法,与静态库相比,动态库具有一些不同的特性和使用方式
特点:
- 运行时链接:动态库在程序启动时或运行过程中被加载,而不是在编译时。
- 共享性:多个程序可以同时使用同一个动态库,节省内存和磁盘空间。
- 更新方便:库的更新只需要替换动态库文件,无需重新编译链接使用该库的程序。
- 版本控制:可以有多个版本的动态库共存,程序可以通过链接时指定的版本号来加载特定版本的库。
- 动态库通常具有
.so
(Shared Object)文件扩展名。 - 节省空间:多个程序共享同一份库文件,减少磁盘占用
- 易于更新:库的更新只需替换文件,无需重新编译程序。
- 更好的兼容性:可以为不同的程序提供不同版本的库。
- 依赖性:程序运行时需要动态库的存在,如果库文件被删除或更新,可能会影响程序的正常运行。
- 启动速度:可能比静态库稍慢,因为需要在运行时解析和加载库。
使用场景:
动态库:适合需要频繁更新的应用程序,或者在有网络连接的环境中,可以方便地下载和更新库
创建动态库:
-
编写源代码: 首先,你需要编写源代码,定义你想要在动态库中共享的函数和全局变量。
-
编译源代码为对象文件: 使用编译器(如gcc)编译源代码为对象文件。确保使用
-fPIC
(Position Independent Code)标志,这样生成的对象文件可以在共享库中使用。gcc -fPIC -c source_file.c -o source_file.o
-
-fPIC
:作用是告知编译器生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库 -
创建动态库: 使用编译器(带有
-shared
标志)或ld
命令来创建动态库。对于gcc,可以这样 -
-shared:Linux在gcc编译时加上 -shared 参数时,目的是使源码编译成动态库 .so 文件
gcc -shared -o libmylib.so source_file.o
或者使用
ld
命令:ld -shared -o libmylib.so source_file.o
-
安装动态库: 将生成的动态库文件
libmylib.so
复制到系统的库目录中,如/usr/lib
或/usr/local/lib
。你可能需要管理员权限来执行此操作。sudo cp libmylib.so /usr/local/lib
-
更新动态链接器的配置: 使用
ldconfig
命令来更新动态链接器的配置,确保系统知道新安装的库的位置。sudo ldconfig
-
编写链接脚本: 如果需要,可以为动态库创建一个链接脚本(
.map
文件),这有助于调试和分析。 -
使用动态库: 在编译需要使用动态库的程序时,使用
-L
标志指定库的路径,使用-l
标志链接库(不包括前缀lib
和后缀.so
)。gcc -o my_program my_program.c -L/usr/local/lib -lmylib
-
设置环境变量(如果库不在标准库路径中): 如果动态库不在标准的库搜索路径中,你可能需要设置
LD_LIBRARY_PATH
环境变量,以便运行时可以找到库。export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH