85、Zlib:集成zlib压缩库
压缩库这东西,说实话在项目里太常见了。日志要压缩、网络传输要压缩、存档文件也要压缩。zlib 算是 C/C++ 世界里最老牌的压缩库之一,几乎成了事实标准。我最早接触它是在做一个嵌入式日志系统的时候,那时候存储空间紧张,一条日志动不动几百字节,不压缩根本存不下几天数据。
今天咱们就聊聊,怎么在 CMake 项目里优雅地集成 zlib。
zlib 是什么?
zlib 是一个通用的数据压缩库,提供了 deflate(压缩)和 inflate(解压)算法。它不处理文件格式,只处理原始数据流。你给它一段内存,它给你压缩好的一段内存。就这么简单。
常见的 gzip 格式、png 图片压缩,底层用的都是 zlib 的算法。所以学会集成 zlib,你就能在很多场景下复用这套技能。
CMake 里集成 zlib 的几种方式
我个人习惯把第三方库的集成方式分成三类:系统预装、源码编译、包管理器。zlib 这三种都支持,咱们一个一个看。
方式一:用 find_package 找系统安装的 zlib
如果你的开发环境里已经装了 zlib(比如 Ubuntu 上 apt install zlib1g-dev),那直接用 CMake 的 find_package 就行。
find_package(ZLIB REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE ZLIB::ZLIB)
target_include_directories(myapp PRIVATE ${ZLIB_INCLUDE_DIRS})
嗯,这里要注意:ZLIB::ZLIB 是 CMake 3.4 之后才支持的导入目标。如果你还在用老版本,那就得用 ${ZLIB_LIBRARIES} 和 ${ZLIB_INCLUDE_DIRS} 变量。不过我个人建议,能升级就升级,导入目标的方式更干净。
dpkg -l | grep zlib(Debian/Ubuntu)或者 rpm -qa | grep zlib(CentOS/RHEL)查一下。
方式二:用 FetchContent 自动下载编译
这种方式我特别喜欢,尤其是在做跨平台项目的时候。你不需要操心每个平台上怎么装 zlib,CMake 自己帮你搞定。
include(FetchContent)
FetchContent_Declare(
zlib
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG v1.3
)
FetchContent_MakeAvailable(zlib)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE zlibstatic)
target_include_directories(myapp PRIVATE ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
这里我链接的是 zlibstatic,也就是静态库。如果你想要动态库,可以改成 zlib。不过我个人习惯用静态库,部署的时候少操心依赖问题。
message(STATUS "Fetching zlib..."),至少让开发者知道当前在干什么。
方式三:用 vcpkg 或 Conan 管理
如果你团队里已经用了包管理器,那集成 zlib 就更简单了。以 vcpkg 为例:
# 先安装 zlib
vcpkg install zlib
# 然后在 CMake 里
find_package(ZLIB REQUIRED)
target_link_libraries(myapp PRIVATE ZLIB::ZLIB)
用 vcpkg 的好处是,它帮你处理了所有平台的编译细节。Windows 上不用愁 MSVC 的运行时库问题,Linux 上也不用愁 ABI 兼容性。我有个项目就是靠 vcpkg 统一了三个平台的依赖管理,省了不少事。
zlib 的核心 API 速览
集成完了,咱们得会用。zlib 的 API 其实不多,核心就这几个:
| 函数 | 作用 |
|---|---|
deflateInit() |
初始化压缩上下文 |
deflate() |
执行压缩 |
deflateEnd() |
结束压缩,释放资源 |
inflateInit() |
初始化解压上下文 |
inflate() |
执行解压 |
inflateEnd() |
结束解压,释放资源 |
使用模式也很固定:先 Init,然后循环调用 deflate/inflate,最后 End。我刚开始用的时候老忘记调 End,结果内存泄漏查了半天。后来养成了习惯,Init 之后立马写对应的 End 调用,中间再填业务逻辑。
一个完整的压缩示例
咱们写个简单的 demo,把一段字符串压缩再解压:
#include <iostream>
#include <vector>
#include <zlib.h>
std::vector<char> compress(const std::string& input) {
uLong srcLen = input.size();
uLong destLen = compressBound(srcLen);
std::vector<char> dest(destLen);
int ret = compress(
reinterpret_cast<Bytef*>(dest.data()), &destLen,
reinterpret_cast<const Bytef*>(input.data()), srcLen
);
if (ret != Z_OK) {
throw std::runtime_error("compress failed");
}
dest.resize(destLen);
return dest;
}
std::string decompress(const std::vector<char>& compressed, uLong srcLen) {
uLong destLen = srcLen;
std::vector<char> dest(destLen);
int ret = uncompress(
reinterpret_cast<Bytef*>(dest.data()), &destLen,
reinterpret_cast<const Bytef*>(compressed.data()), compressed.size()
);
if (ret != Z_OK) {
throw std::runtime_error("decompress failed");
}
return std::string(dest.data(), destLen);
}
int main() {
std::string original = "Hello, zlib! This is a test string for compression.";
auto compressed = compress(original);
auto decompressed = decompress(compressed, original.size());
std::cout << "Original: " << original.size() << " bytes\n";
std::cout << "Compressed: " << compressed.size() << " bytes\n";
std::cout << "Decompressed: " << decompressed.size() << " bytes\n";
std::cout << "Match: " << (original == decompressed ? "yes" : "no") << "\n";
return 0;
}
这段代码里用了 compress 和 uncompress 这两个高级函数,它们内部帮你处理了 Init 和 End。如果你需要流式压缩(比如边读边压),那就得用 deflate/inflate 那套底层 API 了。
zlib 集成知识体系
我把今天讲的内容画了张图,方便你理解整体脉络:
避坑指南
最后分享几个我踩过的坑:
- 我曾经在 Windows 上用 MSVC 编译 zlib,结果链接的时候报了一堆 LNK2019。后来发现是 zlib 的源码里用了
__declspec(dllexport),而我的项目是静态链接。解决方案是定义ZLIB_WINAPI宏,或者在 CMake 里设置target_compile_definitions(zlibstatic PRIVATE ZLIB_WINAPI)。 - 还有一次,我在一个嵌入式 Linux 项目里用 FetchContent 下载 zlib,结果目标板子上没有 git。后来改成用
URL参数直接下载 tar.gz 包才解决。 - 另外,zlib 的
compress函数默认压缩级别是 Z_DEFAULT_COMPRESSION(6)。如果你对压缩率有要求,记得用deflateInit2手动设置级别。级别越高越慢,但压缩率也越高。我一般存盘用 9,网络传输用 3。
好了,zlib 的集成就聊到这儿。说白了就是三步:选方式、写 CMake、调 API。你想想看,是不是挺简单的?