如果一家公司或个人开发者不希望公开他们的源代码(.c
文件),但他们又想让其他人能够使用这些代码的功能,他们可以选择将这些源代码编译成库文件,通常是以静态库(.a
文件)或者动态库(如 .so
或 .dll
文件)的形式发布。
以下是这种做法的大致流程:
- 编写源代码:开发者编写
.c
或.cpp
源代码文件。 - 编写头文件:编写头文件(
.h
或.hpp
文件),其中声明了函数原型、类定义等,这些将被其他开发者包含以使用库中的功能。 - 编译源代码:
- 将源代码编译成目标文件(
.o
文件)。 - 使用链接器将目标文件链接成库文件。
- 静态库(
.a
或.lib
文件):库的代码会被直接嵌入到最终的可执行文件中。 - 动态库(
.so(Linux)
或.dll(Windows)
文件):库的代码在运行时动态加载。
- 静态库(
- 将源代码编译成目标文件(
- 发布库文件和头文件:发布库文件(
.a
,.so
, 或.dll
)以及相应的头文件(.h
或.hpp
)。
其他开发者就可以通过以下步骤使用这些库:
- 包含头文件:在源代码中包含库提供的头文件。
- 链接库文件:
- 如果是静态库(
.a
),则在编译阶段链接。 - 如果是动态库(
.so
或.dll
),则在运行时动态加载,或者在编译时指定。
- 如果是静态库(
这种方法使得开发者可以隐藏源代码的细节,同时允许其他用户利用库的功能。这是很多闭源软件或商业库常见的做法,因为它既保护了知识产权,又允许广泛的应用集成。
下面将演示如何创建一个简单的加法库(静态库),并编写另一个程序来使用这个库。
一、静态库
步骤 1: 创建加法库
1.1 编写头文件 add.h
首先,我们需要创建一个头文件来声明加法函数。
// add.h
#ifndef ADD_H
#define ADD_H
// 加法函数声明
int add(int a, int b);
#endif // ADD_H
1.2 编写源文件 add.c
接下来,我们编写一个源文件来实现加法函数。
// add.c
#include "add.h"
// 加法函数实现
int add(int a, int b) {
return a + b;
}
步骤 2: 编译加法库
我们需要使用 gcc
来编译源文件 add.c
并生成静态库文件。
gcc -c add.c -o add.o
ar rcs libadd.a add.o
这里,gcc -c add.c -o add.o
命令将 add.c
编译成目标文件 add.o
,然后使用 ar
命令将目标文件打包成静态库 libadd.a
。
步骤 3: 编写使用加法库的程序
现在我们需要编写一个程序来使用我们刚刚创建的加法库。
3.1 编写主程序 main.c
// main.c
#include <stdio.h>
#include "add.h"
int main() {
int result = add(5, 3);
printf("The sum is: %d\n", result);
return 0;
}
3.2 编译主程序并链接加法库
最后,我们需要编译 main.c
并链接我们的加法库。
gcc main.c -o main -L. -ladd
这里,-L.
指定当前目录为库文件的搜索路径,-ladd
指定链接库 libadd.a
。
步骤 4: 运行程序
现在你可以运行编译好的程序 main
来测试加法功能。
./main
完整的 Makefile
为了方便,我们可以创建一个简单的 Makefile 来自动化整个构建过程。
# 编译器和编译选项
CC = gcc
CFLAGS = -Wall -Wextra
LDFLAGS = -L./lib -ladd
# 目标可执行文件名称
TARGET = main
# 头文件搜索路径
INCLUDE_PATH = -I ./inc
# 源文件和目标文件
SOURCE_FILES = $(wildcard ./src/*.c)
OBJECTS = $(patsubst ./src/%.c, ./obj/%.o, $(SOURCE_FILES))
# 主要构建规则
$(TARGET): $(OBJECTS)
$(CC) -o ./bin/$(TARGET) $^ $(INCLUDE_PATH) $(LDFLAGS) $(CFLAGS)
# 生成 .o 文件的规则
./obj/%.o: ./src/%.c
$(CC) -c -o $@ $< $(INCLUDE_PATH) $(CFLAGS)
# 清理规则
clean:
rm -f ./bin/$(TARGET) $(OBJECTS)
.PHONY: clean
运行结果
整个过程:
a) 从源代码到目标文件 (add.c
到 add.o
):
- 当你使用
gcc -c add.c -o add.o
命令时,编译器 (gcc
) 将源代码文件add.c
转换成一个目标文件add.o
。
b) 从目标文件到可执行文件 (add.o
到 add.exe
或者到库文件 (add.o
到 libadd.a
)):
- 使用
ar
命令创建的静态库libadd.a
是由多个目标文件(如add.o
)组成的档案文件。每个目标文件都是机器语言形式的,包含了可执行指令和数据。
总结来说:
- 源代码文件 (
add.c
) 是 ASCII 文本文件,包含人类可读的代码。 - 编译后得到的目标文件 (
add.o
) 包含了机器语言指令和数据,以及未解决的外部符号引用。 - 链接后得到的可执行文件 (
main
) 或 静态库文件 (libadd.a
) 是完全链接的二进制文件,可以直接在计算机上运行或被其他程序链接。
二、动态库
步骤1:编译动态库
gcc -shared -fPIC -I../inc -o ../lib/libadd.so add.c
步骤2:编译主程序并链接动态库
gcc -I../inc main.c -L../lib -ladd -o ../bin/main
步骤3:运行程序
./main
这个时候会遇到“cannot open shared object file”报错,不要慌!这是因为动态链接器在运行时未能找到所需的动态库文件。你需要确保动态链接器能够在运行时找到libadd.so
解决方法:临时设置 LD_LIBRARY_PATH 环境变量来指向你的动态库所在的目录,然后再运行。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/dynamic/library
三、静态库和动态库的区别
静态库和动态库是计算机程序中两种不同类型的库文件,它们的主要区别在于链接方式、可执行文件大小、更新方式以及内存占用等方面。
1.链接方式:
- 静态库(Static Library):在编译时,静态库中的代码会被链接到最终的可执行文件中。这意味着,静态库的代码会直接嵌入到可执行文件中。
- 动态库(Dynamic Library/Shared Library):在编译时,动态库中的代码不会被链接到最终的可执行文件中,而是在程序运行时由系统动态加载。这意味着程序运行时需要依赖于动态库的存在。
2.可执行文件大小:
- 静态库:由于静态库的代码被直接包含在可执行文件中,因此可执行文件可能会变得较大。
- 动态库:因为动态库的代码不包含在可执行文件中,所以可执行文件本身的大小较小,但是这取决于动态库的大小。
3.更新方式:
- 静态库:如果对静态库进行了更新,那么所有使用该库的程序都需要重新编译以包含新的库版本。
- 动态库:可以独立于应用程序进行更新,只要接口保持不变,就不需要重新编译应用程序。
4.内存占用
- 静态库:每个使用了相同库的程序都会在其自身的地址空间中包含一份副本,导致可能的内存浪费。
- 动态库:多个程序可以共享同一份动态库的内存映像,从而节省内存资源
相关文章:CMake——GitHub移植项目