14. sizeof 与 strlen 的区别:它们到底在算什么?

说实话,这个问题我面试过不少人,十有八九会答错。有人觉得「sizeof就是算长度的」,有人觉得「strlen就是sizeof的简化版」——嗯,这两种理解都不对。今天咱们就把这两个家伙彻底掰扯清楚。

14.1 先看一个让人懵圈的代码

我直接上代码,你先猜猜输出是什么:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello";
    char *p = str;
    
    printf("sizeof(str) = %zu\n", sizeof(str));
    printf("strlen(str) = %zu\n", strlen(str));
    printf("sizeof(p) = %zu\n", sizeof(p));
    printf("strlen(p) = %zu\n", strlen(p));
    
    return 0;
}

在64位系统上,输出是这样的:

sizeof(str) = 6
strlen(str) = 5
sizeof(p) = 8
strlen(p) = 5

看到了吗?sizeof(str) 是6,strlen(str) 是5。差在哪?差在结尾那个看不见的 '\0'。而 sizeof(p) 是8,因为指针本身占8个字节(64位系统),跟字符串长度半毛钱关系都没有。

核心区别一句话:

  • sizeof 是运算符,算的是「变量或类型在内存中占了多少字节」
  • strlen 是函数,算的是「字符串从开头到 '\0' 之间有多少个字符」

14.2 sizeof 到底在算什么?

sizeof 是编译时求值的。编译器看到 sizeof,直接查类型信息,不需要运行程序就能知道结果。所以它算的是「内存布局的大小」,不是「内容的长短」。

举个例子:

int arr[10];
printf("%zu\n", sizeof(arr));   // 40,因为 int 占4字节,10个就是40
printf("%zu\n", sizeof(arr[0])); // 4,一个int的大小

数组名在这里代表整个数组,所以 sizeof(arr) 返回整个数组的字节数。但一旦数组名退化成指针,情况就变了:

void func(int arr[]) {
    printf("%zu\n", sizeof(arr));  // 8,arr已经退化成指针了!
}

我在项目中遇到过好几次这种坑。有个同事在函数里用 sizeof(arr) 去算数组长度,结果传进来的数组永远「只有8个字节」——调试了一下午才发现是数组退化的问题。

注意:sizeof 对数组和指针的行为完全不同。数组名在 sizeof 中不会退化,但在函数参数中会退化成指针。这是C语言里最容易踩的坑之一。

14.3 strlen 到底在算什么?

strlen 是运行时函数。它从你给的地址开始,一个字节一个字节地往后数,直到碰到 '\0' 为止。它不关心你分配了多少内存,只关心 '\0' 在哪里。

看这个例子:

char buf[100] = "Hello";
printf("%zu\n", strlen(buf));  // 5,只数到'\0'为止
printf("%zu\n", sizeof(buf));  // 100,整个数组的大小

buf 有100个字节的空间,但 strlen 只数了5个字符就停了,因为第6个字符是 '\0'。sizeof 则老老实实告诉你:这个数组占了100个字节。

我曾经接手过一个项目,里面有一段代码:

char *msg = malloc(256);
strcpy(msg, "OK");
printf("length = %zu\n", strlen(msg));  // 2,没问题
printf("size = %zu\n", sizeof(msg));    // 8,指针大小,不是256!

那个程序员用 sizeof(msg) 去判断缓冲区是否够用,结果可想而知。嗯,后来我改成了 #define BUF_SIZE 256,用宏来管理缓冲区大小。

14.4 一张图看懂核心逻辑

下面这张图展示了 sizeof 和 strlen 在处理同一个字符串时的不同路径:

sizeof vs strlen 处理流程对比 输入: "Hello" sizeof 运算符 编译时求值,查类型信息 strlen 函数 运行时求值,逐字节扫描 对数组: 整个数组字节数 对指针: 指针本身大小(8字节) 从起始地址到 '\0' 的字符数 不关心内存大小,只找 '\0' 结果: 6 (含 '\0') 结果: 5 (不含 '\0')

14.5 常见误区与避坑指南

我总结了几个最常见的错误,你看看自己中过几个:

误区 错误写法 正确做法
用 sizeof 算字符串长度 sizeof("Hello") → 6 strlen("Hello") → 5
在函数内用 sizeof 算数组长度 void f(int a[]) { sizeof(a); } 把数组长度作为参数传进来
对指针用 sizeof 以为能拿到缓冲区大小 char *p = malloc(100); sizeof(p); 用宏或变量记录分配的大小
混淆 sizeof 和 strlen 的返回值类型 两者都返回 size_t,但含义不同 sizeof 是字节数,strlen 是字符数

我的个人习惯:

  • 需要知道缓冲区大小时,用 sizeof(仅限数组)
  • 需要知道字符串有效长度时,用 strlen
  • 动态分配的内存,一定用宏或变量记住大小,别指望 sizeof 指针

14.6 一个容易忽略的细节

strlen 返回的是 size_t,这是个无符号类型。如果你这么写:

if (strlen(s) - 10 > 0) {
    // 你以为长度大于10才进来?
}

strlen(s) 小于10时,无符号数减10会变成一个巨大的正数,条件永远为真。我曾经在代码审查时看到过这种写法,当时就提醒了那位同事。正确的写法是:

if (strlen(s) > 10) {
    // 这样写才安全
}

14.7 总结一下

sizeof 和 strlen,一个编译时,一个运行时;一个算内存布局,一个算字符个数。搞混了,轻则逻辑错误,重则缓冲区溢出。你想想看,如果拿 sizeof 的结果去分配内存,或者拿 strlen 的结果去拷贝整个数组,后果都不堪设想。

记住一句话:sizeof 问的是「你有多大空间」,strlen 问的是「你有多长内容」。两者各司其职,别让它们干对方的活。


公众号:蓝海资料掘金营,微信deep3321