C/C++静态库和动态库(共享库)

如果一家公司或个人开发者不希望公开他们的源代码(.c 文件),但他们又想让其他人能够使用这些代码的功能,他们可以选择将这些源代码编译成库文件,通常是以静态库(.a 文件)或者动态库(如 .so.dll 文件)的形式发布。

以下是这种做法的大致流程:

  1. 编写源代码:开发者编写 .c.cpp 源代码文件。
  2. 编写头文件:编写头文件(.h.hpp 文件),其中声明了函数原型、类定义等,这些将被其他开发者包含以使用库中的功能。
  3. 编译源代码
    • 将源代码编译成目标文件(.o 文件)。
    • 使用链接器将目标文件链接成库文件。
      • 静态库(.a.lib 文件):库的代码会被直接嵌入到最终的可执行文件中。
      • 动态库(.so(Linux).dll(Windows) 文件):库的代码在运行时动态加载。
  4. 发布库文件和头文件:发布库文件(.a, .so, 或 .dll)以及相应的头文件(.h.hpp)。

其他开发者就可以通过以下步骤使用这些库:

  1. 包含头文件:在源代码中包含库提供的头文件。
  2. 链接库文件
    • 如果是静态库(.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.cadd.o)

  • 当你使用 gcc -c add.c -o add.o 命令时,编译器 (gcc) 将源代码文件 add.c 转换成一个目标文件 add.o

b) 从目标文件到可执行文件 (add.oadd.exe 或者到库文件 (add.olibadd.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移植项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值