varint 是 protocol buffers 中用于紧凑编码无符号整数的变长字节序列,c++ 解析易出错因无内置支持,手动解析需逐字节处理 continuation bit、防溢出、限长 10 字节,并区分 int32/uint32/sint32 对应的 zigzag 解码。

Varint 是什么,为什么 C++ 解析时容易出错
Varint 不是标准类型,而是 Protocol Buffers 等二进制协议中用于紧凑编码整数的变长字节序列:小数字用 1 字节,大数字最多用 10 字节(64 位)。C++ 没有内置解析函数,直接 memcpy 或强制类型转换会读错字节顺序、忽略 continuation bit(最高位 1 表示后续还有字节),导致解析结果远小于真实值,甚至死循环。
常见错误现象:ReadVarint64 返回 0 或极小值;读取后指针未正确前移,下一次解析从中间字节开始;对负数直接解析(Varint 只编码无符号整数,有符号数需额外 ZigZag 解码)。
手动解析 Varint 的核心逻辑与边界处理
本质是按字节读取,每次取低 7 位拼接,直到某字节最高位为 0。关键在指针移动、溢出检查和最大长度限制(防恶意输入)。
- 必须逐字节判断
byte & 0x80是否为真,决定是否继续读 - 每次拼接前检查是否已读满 10 字节(
uint64_t最多需 10 字节 Varint),超限立即返回错误 - 用
static_cast<uint8_t></uint8_t>强制截断,避免符号扩展干扰低 7 位提取 - 拼接时用
value |= (static_cast<uint64_t>(byte & 0x7F) ,shift 每轮加 7</uint64_t>
示例片段(不依赖 protobuf 库):
bool ReadVarint64(const char** ptr, const char* limit, uint64_t* out) {
const char* p = *ptr;
uint64_t value = 0;
int shift = 0;
while (p (*p++);
value |= static_cast<uint64_t>(byte & 0x7F) <h3>ProtoBuf 中的 <code>ReadVarint32</code> 和 <code>ReadVarint64</code> 怎么用</h3>
<p>如果你已在项目中引入 protobuf(如 <code>google/protobuf/io/coded_stream.h</code>),优先复用其经过充分测试的实现,它们已处理字节序、缓冲区边界、恶意输入等细节。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<ul>
<li>必须先构造 <code>google::protobuf::io::CodedInputStream</code>,传入原始 buffer 和 size</li>
<li>
<code>ReadVarint32</code> 返回 <code>bool</code>,成功时通过引用参数输出值;失败可能是 buffer 不足或格式错误</li>
<li>注意:该类默认启用 aliasing(允许 buffer 复用),若 buffer 生命周期短于 stream,需调用 <code>SetTotalBytesLimit</code> 防止越界读</li>
</ul>
<p>简短调用示例:</p>
<pre class="brush:php;toolbar:false;">const char* data = ...;
int size = ...;
google::protobuf::io::ArrayInputStream input(data, size);
google::protobuf::io::CodedInputStream coded(&input);
coded.SetTotalBytesLimit(1024 * 1024); // 防止过大 payload
uint32_t val;
if (!coded.ReadVarint32(&val)) {
// 解析失败
}
有符号整数(int32/int64)要额外做 ZigZag 解码
Varint 编码的是无符号整数,但 proto 中的 sint32 / sint64 使用 ZigZag 编码映射负数到正数空间(例如 -1 → 1,-2 → 3),再用 Varint 存储。漏掉这步会导致所有负数全解析成极大正数。
ZigZag 公式固定:
-
sint32:(n > 31) -
sint64:(n > 63)
实际使用时,先用前述方法读出 uint32_t 或 uint64_t,再套公式。protobuf 库的 ReadVarint32 等函数不自动 ZigZag,必须手动调用 google::protobuf::internal::WireFormatLite::ZigZagDecode32 等辅助函数,或自己实现。
最易被忽略的是:Varint 本身没有“符号”概念,所有符号处理都发生在上层协议定义层面。哪怕只解析一个字段,也得先确认它在 .proto 中声明的是 int32、uint32 还是 sint32——三者底层 Varint 数据相同,解码逻辑却完全不同。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!









