
C 中 sizeof 与 strlen 的区别从运算符到函数彻底讲清两者的差异一、引言两个看似相似却本质不同的工具sizeof和strlen是 C/C 中经常被拿来比较的一对概念。它们都可以用来获取某种“大小”但这个“大小”的含义完全不同。很多初学者在处理字符串时会混淆这两个工具导致缓冲区溢出、逻辑错误等问题。sizeof是编译期运算符返回的是类型或变量占用的内存字节数。strlen是运行时函数返回的是C 风格字符串的字符个数(不含结尾空字符)。理解它们的区别是写出安全、正确的 C 代码的基本功。二、核心区别速览表| 对比维度 | sizeof | strlen ||----------|--------|--------|| 本质 | 运算符(operator)不是函数 | 标准库函数(function) || 求值时机 |编译期(除 VLA 外) |运行期|| 头文件 | 无需头文件内置关键字 |cstring或string.h|| 计算对象 | 类型名、变量名、表达式 |const char*指向的字符串 || 计算内容 | 类型或变量占用的内存字节数| 字符串中字符的个数(不含 \0) || 包含 \0 | 是(如果 \0 在作用域内) | 否(遇到 \0 就停止) || 参数能否为 NULL | 可以返回类型大小 |不能会导致段错误 || 数组退化 | 不退化(仅在数组的作用域内) | 传入的是指针已退化 |三、求值时机的根本差异3.1 sizeof编译期运算int arr[10]; std::cout sizeof(arr); // 40在编译期就确定了 int n 10; int arr2[n]; // C99 VLAC 标准不支持 // sizeof(arr2) 在 C 中是编译期常量除非编译器扩展支持 VLAsizeof在编译期就计算完成结果是一个编译期常量。这意味着sizeof的结果可以用于数组大小、模板参数等需要编译期常量的场景sizeof不实际执行表达式只推导表达式的类型3.2 strlen运行时计算const char* str Hello; std::cout strlen(str); // 5运行时从头遍历到 \0strlen在运行时执行它会从传入的地址开始逐字节遍历内存直到遇到\0为止然后返回遍历过的字符个数。strlen 函数执行代码strlen 函数执行代码alt[不是 \0][是 \0]loop[逐字节检查]strlen(ptr)从 ptr 指向的地址开始当前字节是 \0?计数器 1移动到下一字节停止遍历返回计数器的值四、处理字符串时的核心区别这是最容易产生混淆的场景。4.1 代码示例#include cstring #include iostream int main() { char str1[] Hello; // 字符串 Hello末尾自动添加 \0 const char* str2 Hello World; char str3[20] Hi; // 大小为 20初始化为 Hi\0 // sizeof 返回的是变量占用的总内存字节数 std::cout sizeof(str1) std::endl; // 输出 6 (5 个字符 \0) std::cout sizeof(str2) std::endl; // 输出 8 (指针的大小) std::cout sizeof(str3) std::endl; // 输出 20 (数组总大小) // strlen 返回的是字符串的字符个数(不含 \0) std::cout strlen(str1) std::endl; // 输出 5 std::cout strlen(str2) std::endl; // 输出 11 std::cout strlen(str3) std::endl; // 输出 2 }4.2 重点数组与指针在 sizeof 下的不同表现char arr[] Hello World; char* ptr arr; std::cout sizeof(arr); // 12: 整个数组的大小(11 个字符 \0) std::cout sizeof(ptr); // 8: 指针变量本身的大小 std::cout strlen(arr); // 11: 字符串长度 std::cout strlen(ptr); // 11: 字符串长度两者相同对于strlen来说arr和ptr传入后都被当作const char*结果相同。对于sizeof来说arr是数组类型返回整个数组的字节数ptr是指针类型返回指针本身的字节数。strlen(str1) 的范围前 5 个字符, 不包含 \0sizeof(str1) 的范围全部 6 字节str1[] HelloHello\0五、安全性对比5.1 strlen 不能接受 NULLconst char* ptr nullptr; std::cout sizeof(ptr); // OK: 输出 8 (指针的大小) std::cout strlen(ptr); // 危险段错误对 NULL 解引用sizeof(ptr)在编译期就确定结果根本不需要解引用指针。strlen(ptr)会尝试读取ptr指向的内存传入 NULL 会导致未定义行为。5.2 strlen 可能越界读取char buffer[10]; // buffer 未初始化内容随机 // 没有 \0 的情况下strlen 会一直读到碰到某个 \0 为止 std::cout strlen(buffer); // 危险可能读取越界5.3 安全使用建议// 使用 strnlen (POSIX) 或 std::string 避免风险 char buffer[10] {0}; // 确保初始化 std::cout strlen(buffer); // 安全输出 0 // 更推荐的方式 std::string str Hello; std::cout str.size(); // 安全类型明确六、数组传参时的退化陷阱6.1 经典错误void printSizeInfo(int arr[]) { std::cout sizeof(arr) sizeof(arr) std::endl; // 输出 8 // arr 已退化为 int* 指针 } void printLenInfo(const char* str) { std::cout sizeof(str) sizeof(str) std::endl; // 输出 8 std::cout strlen(str) strlen(str) std::endl; // 运行时计算 } int main() { int arr[100]; printSizeInfo(arr); // sizeof 输出 8不是 400 const char* msg Hello World; printLenInfo(msg); // sizeof 输出 8strlen 输出 11 }6.2 如何保留数组大小信息// 方法一使用模板 templatesize_t N void func(int (arr)[N]) { std::cout sizeof(arr) std::endl; // 正确N * sizeof(int) } // 方法二使用 std::array void func(std::arrayint, 100 arr) { std::cout sizeof(arr) std::endl; // 正确 // 或者直接用 arr.size() } // 方法三额外传递大小参数 void func(int* arr, size_t size) { // 需要手动维护 size }七、性能对比| 方面 | sizeof | strlen ||------|--------|--------|| 时间复杂度 | O(1) | O(n)n 为字符串长度 || 求值阶段 | 编译期 | 运行期 || 运行时开销 | 零开销 | 需要遍历整个字符串 || 对程序性能影响 | 无 | 长字符串可能有一定开销 |// sizeof: 编译期完成运行时就是常量 int arr[1000]; int size sizeof(arr); // 编译后等价于 int size 4000; // strlen: 运行时需要遍历 const char* longStr a very long string ...; int len strlen(longStr); // 运行时要逐个字符检查八、决策流程图获取变量/类型占用的内存字节数获取 C 风格字符串的字符个数char* / const char*是否std::string需要获取大小或长度目标是什么?使用 sizeof使用 strlen编译期计算结果可用于编译期常量对所有类型有效数据类型是?s是否为 NULL?不安全: 段错误运行时遍历计算时间复杂度 O(n)不包含末尾 \\0使用 .size() 或 .length()推荐: O(1) 时间复杂度类型安全九、代码综合示例#include iostream #include cstring int main() { // 基本使用 char str[] C Programming; std::cout sizeof(str) sizeof(str) std::endl; // 输出: sizeof(str) 16 // 原因: 15 个字符 \0 16 字节 std::cout strlen(str) strlen(str) std::endl; // 输出: strlen(str) 15 // 原因: 15 个可见字符 // 空字符串 char empty[] ; std::cout sizeof(empty) sizeof(empty) std::endl; // 1: 只有 \0 std::cout strlen(empty) strlen(empty) std::endl; // 0: 没有字符 // 中间有 \0 的字符串 char mid[] Hello\0World; // 数组包含 Hello\0World\0 std::cout sizeof(mid) sizeof(mid) std::endl; // 12 std::cout strlen(mid) strlen(mid) std::endl; // 5: 遇到第一个 \0 就停止 // 指针 vs 数组 const char* ptr str; std::cout sizeof(ptr) sizeof(ptr) std::endl; // 8: 指针大小 std::cout strlen(ptr) strlen(ptr) std::endl; // 15: 字符串长度 // 其他类型的 sizeof int num 42; double d 3.14; std::cout sizeof(int) sizeof(int) std::endl; // 4 std::cout sizeof(num) sizeof(num) std::endl; // 4 std::cout sizeof(double) sizeof(double) std::endl; // 8 std::cout sizeof(d) sizeof(d) std::endl; // 8 // strlen 不能用于非字符串类型 // strlen(num); // 编译错误 }十、总结sizeof和strlen的核心区别可以浓缩为以下五点本质不同sizeof是 C 的内置运算符strlen是 C 标准库函数。两者处于完全不同的抽象层次。求值时机不同sizeof在编译期完成计算是编译期常量strlen在运行时逐字节遍历内存时间复杂度为 O(n)。计算内容不同sizeof返回的是类型或变量所占用的内存总字节数包含末尾的\0(如果在作用范围内)strlen返回的是字符串中可见字符的个数遇到\0就停止不包含\0本身。适用对象不同sizeof适用于任何类型(int、double、struct、class、数组、指针等)strlen仅适用于以\0结尾的 C 风格字符串(const char*或char[])。安全性不同sizeof不进行解引用操作传入什么都不会导致运行时崩溃strlen传入nullptr会段错误传入未初始化的字符数组可能读取越界。在现代 C 中强烈建议使用std::string替代 C 风格字符串使用.size()或.length()方法获取长度。sizeof在模板元编程、内存分配、与 C API 交互等场景中仍然不可或缺但日常开发中应该越来越少地直接使用strlen。