Varint是Protocol Buffers定义的紧凑整数编码方式,小数值用更少字节(如0仅占1字节),大数值逐步扩展,节省网络带宽;C++需手动实现或借助Protobuf库,编码时取低7位、高位标续位,解码须防越界、超长和ZigZag误处理。

Varint 是什么,为什么 C++ 网络传输要用它
Varint 不是 C++ 标准库内置类型,而是 Google Protocol Buffers 定义的一种紧凑整数编码方式:小数值用更少字节表示(比如 0 只占 1 字节),大数值才逐步扩展。这对网络传输很关键——多数 ID、状态码、长度字段实际值都很小,用 int32_t(固定 4 字节)或 int64_t(固定 8 字节)会浪费带宽。
直接手写 Varint 编解码不难,但必须注意字节序、符号处理和边界检查。C++ 没有现成的 encode_varint 函数,得自己实现或借力 Protobuf 库。
手动实现无符号整数的 Varint 编码(uint32_t / uint64_t)
Protobuf 的 Varint 只支持无符号整数;如果要编码有符号数(如 int32_t),得先做 ZigZag 编码(后面会提)。先看最常用的 uint64_t 编码:
核心逻辑:每次取低 7 位作为数据,最高位设为 1 表示“还有后续字节”,直到只剩 ≤7 位时设最高位为 0。
立即学习“C++免费学习笔记(深入)”;
- 编码时从低位开始写,所以结果字节流是 little-endian 风格(但不是传统意义的字节序,而是“变长单元序”)
- 务必检查输入是否为
0:它应编码为单字节0x00,不是空字节或越界读 - 输出缓冲区至少预留 10 字节(
uint64_t最坏情况需 10 字节,因为 64 ÷ 7 ≈ 9.14 → 向上取整为 10) - 不要用
std::vector<uint8_t>::push_back</uint8_t>在循环里反复扩容,影响性能;建议预分配或传入固定大小缓冲区 + 返回实际长度
示例编码函数片段:
size_t encode_varint(uint64_t value, uint8_t* buf) {
size_t len = 0;
while (value >= 0x80) {
buf[len++] = static_cast<uint8_t>(value | 0x80);
value >>= 7;
}
buf[len++] = static_cast<uint8_t>(value);
return len;
}
解码时容易崩溃的三个坑:buf 越界、负数误读、超长编码
解码比编码更容易出错,尤其在网络收包场景下,buf 可能被截断或含脏数据:
- 必须限制最多读 10 字节(对
uint64_t),否则恶意构造的全0xFF输入会导致无限循环或越界访问 - 不能假设输入是“合法 Protobuf 流”——真实网络包可能丢包、粘包,解码前要确认可用字节数 ≥ 当前已读长度
- 如果原始数是有符号的(如
int32_t),直接解成uint32_t再强转会出错;必须先用 ZigZag 解码:(n >> 1) ^ -(n & 1) - Protobuf 的
int32字段底层存的是 ZigZag 编码的uint32,不是原值;漏这步会导致负数全变成极大正数(比如-1变成0xFFFFFFFF)
安全解码示意(带长度检查):
bool decode_varint(const uint8_t* buf, size_t max_len, uint64_t* out, size_t* out_len) {
uint64_t val = 0;
size_t len = 0;
int shift = 0;
while (len < max_len && shift < 64) {
uint8_t byte = buf[len++];
val |= static_cast<uint64_t>(byte & 0x7F) << shift;
if ((byte & 0x80) == 0) {
*out = val;
*out_len = len;
return true;
}
shift += 7;
}
return false; // 解码失败:超长或截断
}
用 Protobuf 自动生成代码更省心,但要注意链接和 ABI 兼容性
如果你项目已用 Protobuf,直接定义 .proto 文件,让 protoc 生成 C++ 类,它内部的 SerializeToArray 和 ParseFromArray 就自动处理 Varint(包括 ZigZag、嵌套、字段标签等)。但这带来新约束:
- 必须静态链接
libprotobuf或确保运行时 ABI 版本匹配,否则std::bad_cast或静默解析失败很常见 - 生成的
SerializePartialToArray比完整版更快(跳过 required 字段检查),适合高性能场景 - 字段类型选
int32还是sint32很关键:int32用普通 Varint(负数编码极长),sint32才用 ZigZag —— 别凭名字猜,查文档 - 不推荐只为 Varint 去引入 Protobuf;纯轻量场景,手写 50 行编码/解码函数更可控
真正麻烦的从来不是算法本身,而是跨语言对接时对方用的是不同 ZigZag 实现、或把 signed/unsigned 混着传——这种问题往往在压测后期才暴露,且难以复现。


















