02 / 80 · C++11 · 约 7 分钟
sizeof 与 strlen:容量、终止符和逻辑长度
sizeof 测量类型占用,strlen 沿字符序列寻找第一个零字符,两者回答不同问题。字符串字面量包含结尾零字符,嵌入零字符会提前结束 strlen;指针本身不记录容量,也不保证指向合法字符串。
三个长度不要混为一谈
对 char text[20] = "cat",数组容量是二十个 char,当前 C 字符串长度是三个字符,存储这段字符串至少需要四个 char,因为末尾还有零字符。sizeof(text) 返回 20;std::strlen(text) 返回 3,不包含终止符。
sizeof 根据表达式类型得出大小,不扫描内容。strlen 的语义则要求找到首个零字符;通常需要与前缀长度成正比的扫描,虽然编译器可能把已知字面量的调用折叠成常量。优化不会使 strlen 变成能查询任意缓冲区容量的工具。
嵌入零字符与指针
字面量 "ab\0cd" 含有五个显式字符,再加一个自动附加的终止符,总共六个 char。strlen 在中间的零字符处停止,只返回 2;数组里后面的 c 和 d 仍然存在,并没有被删掉。
把数组赋给 const char* 后,sizeof 测量的是指针,不再是六个元素。strlen 仍会从指针指向的位置扫描,因此这两个表达式既不等价,也不能互换。UTF-8 文本还需区分编码字节数与用户看到的字符数,strlen 不理解 Unicode 字符边界。
让接口表达真实数据模型
当数据保证以零字符结尾时,C 字符串接口可以使用 strlen。对于网络帧、压缩数据和包含零字节的文本片段,应传入显式长度。示例用 std::string(raw, sizeof(raw) - 1) 保留中间零字符,仅排除字面量自动添加的最后一个零。
string 的 size 表示保存的 char 元素数,不需要扫描终止符。C++17 的 string_view 也能保存长度,但不拥有数据且不承诺视图末尾有零字符。把 view.data() 直接交给要求 C 字符串的接口,可能越过视图边界读取。
容易答错的地方
- 对不含可达终止符的字符数组调用 strlen 会越界读取并产生未定义行为;不能靠结果碰巧正确证明安全。
- strlen(nullptr) 非法;sizeof(pointer) 虽然合法,却也无法证明该指针可解引用。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <cstring>
#include <iostream>
#include <string>
int main() {
const char raw[] = "ab\0cd";
const std::string text(raw, sizeof(raw) - 1);
assert(sizeof(raw) == 6);
assert(std::strlen(raw) == 2);
assert(text.size() == 5);
assert(text[3] == 'c');
std::cout << sizeof(raw) << ' ' << std::strlen(raw)
<< ' ' << text.size() << '\n';
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-sizeof-strlen.cpp -o example && ./example预期结果
6 2 5
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
char buffer[8] = {'o', 'k', '\0', 'x'}; 的 sizeof、strlen 分别是多少?如何构造含前四个元素的 string?
查看参考答案
sizeof(buffer) 是 8,strlen(buffer) 是 2,因为第三个元素已是零字符。使用 std::string s(buffer, 4),得到 size 为 4 的字符串,其中 s[2] 是零字符、s[3] 是 x。只写 std::string s(buffer) 会采用终止符约定,仅保存 ok。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。