云计算百科
云计算领域专业知识百科平台

sizeof 和 strlen 到底差在哪?四段代码把 C 语言数组指针按在地上摩擦(附 64 位输出 + 面试题)

先说结论:这四段代码,表面上是“打印几个数”,实际上是 C 语言笔试面试里最经典的“指针类型识别 + 数组退化 + 未定义行为”三合一。很多人第一次做,能把 strlen(*arr) 写成 1,把 sizeof(&arr + 1) 写成 8 却不知道为什么,最后面试官一句“你确定?”就开始冒汗。

今天我不只给你答案,还要把背后的“地图”铺开。教育学里有个词叫“先行组织者”,意思是先给你一个认知框架,再往里填细节。否则你一次面对 sizeof、strlen、数组名退化、指针加减、字符串字面量,认知负荷直接拉满,看多少遍都容易忘。

所以先记住三句话:

  • sizeof 是运算符,不是函数。它只看类型,不看里面存了什么字符串。

  • strlen 是函数。它从你给的地址开始,一个字节一个字节找 '\\0'。

  • 数组名在大多数表达式里会退化成首元素指针,但 sizeof(arr)、&arr 是例外。

  • 下面默认环境是 64 位 Linux/GCC/Clang,指针大小 8 字节。如果是 32 位环境,所有指针大小从 8 变成 4。另外,原代码用 %d 打印 sizeof 和 strlen 的返回值,严格说格式不匹配,正确应该用 %zu。笔试里为了省事常写 %d,但工程里别这么干。


    一、先把“演员表”认清楚

    c

    char arr[] = "abcdef";

    这行代码等价于:

    c

    char arr[7] = {'a', 'b', 'c', 'd', 'e', 'f', '\\0'};

    注意,"abcdef" 是 6 个可见字符,但字符串字面量末尾自带一个 '\\0',所以数组长度是 7。

    c

    char* p = "abcdef";

    这行代码里,p 是一个指针变量,指向字符串字面量 "abcdef" 的首字符 'a'。字符串字面量通常放在只读区,所以更规范写法是:

    c

    const char* p = "abcdef";

    你如果尝试 p[0] = 'x';,那是未定义行为,可能直接段错误。

    现在演员有:

    • arr:数组名,类型 char[7]

    • arr + 0:表达式里数组名退化,类型 char*

    • *arr:等价 arr[0],类型 char

    • &arr:指向整个数组的指针,类型 char (*)[7]

    • &arr[0]:首元素地址,类型 char*

    • p:指针变量,类型 char*

    • *p:p 指向的字符,类型 char

    • &p:指针变量 p 自己的地址,类型 char**

    这些类型一乱,后面全乱。


    二、代码一:sizeof 版字符数组

    c

    #include <stdio.h>

    int main()
    {
    char arr[] = "abcdef";
    printf("%d\\n", sizeof(arr));
    printf("%d\\n", sizeof(arr + 0));
    printf("%d\\n", sizeof(*arr));
    printf("%d\\n", sizeof(arr[1]));
    printf("%d\\n", sizeof(&arr));
    printf("%d\\n", sizeof(&arr + 1));
    printf("%d\\n", sizeof(&arr[0] + 1));
    return 0;
    }

    逐行拆:

    表达式类型64 位结果解释
    sizeof(arr) char[7] 7 整个数组大小,7 个 char
    sizeof(arr + 0) char* 8 数组名退化,加 0 还是指针
    sizeof(*arr) char 1 *arr 就是 arr[0]
    sizeof(arr[1]) char 1 单个字符
    sizeof(&arr) char (*)[7] 8 指向数组的指针,还是指针
    sizeof(&arr + 1) char (*)[7] 8 指针加 1,类型不变
    sizeof(&arr[0] + 1) char* 8 首元素指针加 1,还是指针

    所以 64 位输出是:

    text

    7
    8
    1
    1
    8
    8
    8

    这里最容易被忽略的是 &arr + 1。&arr 的类型是 char (*)[7],它指向整个数组。&arr + 1 不是跳过 1 个字节,而是跳过整个数组 7 个字节。但 sizeof 只关心类型,它不会真的去访问内存,所以结果仍然是指针大小 8。

    面试常问:

    arr 和 &arr 的值一样吗?
    答:大多数情况下地址值一样,但类型不一样,步长不一样。arr + 1 跳 1 字节,&arr + 1 跳 7 字节。


    三、代码二:strlen 版字符数组,这里开始有坑

    c

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

    int main()
    {
    char arr[] = "abcdef";
    printf("%d\\n", strlen(arr));
    printf("%d\\n", strlen(arr + 0));
    printf("%d\\n", strlen(*arr));
    printf("%d\\n", strlen(arr[1]));
    printf("%d\\n", strlen(&arr));
    printf("%d\\n", strlen(&arr + 1));
    printf("%d\\n", strlen(&arr[0] + 1));
    return 0;
    }

    逐行看:

    表达式结果解释
    strlen(arr) 6 从 'a' 开始数到 '\\0' 前,6 个字符
    strlen(arr + 0) 6 同上
    strlen(*arr) 未定义行为 *arr 是字符 'a',ASCII 97。strlen 会把 97 当地址
    strlen(arr[1]) 未定义行为 arr[1] 是 'b',ASCII 98,同样被当地址
    strlen(&arr) 通常 6 &arr 地址值等于 arr,但类型是 char (*)[7],类型不匹配
    strlen(&arr + 1) 未定义行为 跳过整个数组,指向数组后面,越界读
    strlen(&arr[0] + 1) 5 从 'b' 开始数,剩下 bcdef,长度 5

    所以正常能确定的是:

    text

    6
    6
    崩溃/未定义
    崩溃/未定义
    6(但不建议依赖)
    未定义
    5

    这里必须重点讲 strlen(*arr)。

    strlen 的原型是:

    c

    size_t strlen(const char *str);

    它要的是地址。你给它 *arr,它拿到的是字符 'a',也就是整数 97。于是它把内存地址 0x0000000000000061 当成字符串起点,去那里找 '\\0'。这地址通常不可访问,所以程序大概率直接 segmentation fault。

    这类错误在面试里非常爱考,因为它能区分“你只是背了答案”还是“你真的知道指针和字符的区别”。

    strlen(&arr) 也值得说一句。&arr 类型是 char (*)[7],传给 strlen 类型不兼容,编译器一般会警告。只是它的地址值和 arr 相同,所以运行时很多环境会输出 6。但工程里不要这么写,考试里如果问“输出什么”,可以答“常见环境是 6,但类型不匹配,规范上应避免”。


    四、代码三:sizeof 版字符指针

    c

    #include <stdio.h>

    int main()
    {
    char* p = "abcdef";
    printf("%d\\n", sizeof(p));
    printf("%d\\n", sizeof(p + 1));
    printf("%d\\n", sizeof(*p));
    printf("%d\\n", sizeof(p[0]));
    printf("%d\\n", sizeof(&p));
    printf("%d\\n", sizeof(&p + 1));
    printf("%d\\n", sizeof(&p[0] + 1));
    return 0;
    }

    这次 p 是指针变量,不是数组。

    表达式类型64 位结果解释
    sizeof(p) char* 8 指针变量大小
    sizeof(p + 1) char* 8 指针加 1,还是指针
    sizeof(*p) char 1 p 指向的字符 'a'
    sizeof(p[0]) char 1 等价 *p
    sizeof(&p) char** 8 指针变量自己的地址
    sizeof(&p + 1) char** 8 指向指针的指针,加 1 还是指针
    sizeof(&p[0] + 1) char* 8 &p[0] 是 char*,加 1 还是 char*

    输出:

    text

    8
    8
    1
    1
    8
    8
    8

    注意,sizeof(p) 不是 6,也不是 7。p 只是指针,它不携带“我指向的字符串有多长”的信息。这是 C 语言和很多高级语言不一样的地方。C 里字符串长度要靠 '\\0' 标记,或者你另外存一个长度变量。


    五、代码四:strlen 版字符指针,坑更深

    c

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

    int main()
    {
    char* p = "abcdef";
    printf("%d\\n", strlen(p));
    printf("%d\\n", strlen(p + 1));
    printf("%d\\n", strlen(*p));
    printf("%d\\n", strlen(p[0]));
    printf("%d\\n", strlen(&p));
    printf("%d\\n", strlen(&p + 1));
    printf("%d\\n", strlen(&p[0] + 1));
    return 0;
    }

    表达式结果解释
    strlen(p) 6 从 'a' 开始数
    strlen(p + 1) 5 从 'b' 开始数
    strlen(*p) 未定义行为 *p 是 'a',被当地址
    strlen(p[0]) 未定义行为 同上
    strlen(&p) 未定义/不确定 &p 是 p 变量自己的地址,不是字符串地址
    strlen(&p + 1) 未定义行为 越界读
    strlen(&p[0] + 1) 5 等价 strlen(p + 1)

    能确定的是:

    text

    6
    5
    崩溃/未定义
    崩溃/未定义
    不确定/未定义
    未定义
    5

    重点说 strlen(&p)。

    p 是一个指针变量,它自己占 8 字节,里面存的是字符串字面量的地址。&p 是“存放这个指针变量的内存地址”,类型是 char**。strlen(&p) 会把 p 这个变量所占的内存字节当成字符串来数。它会从栈上读字节,直到遇到某个 0 字节。结果取决于栈布局、地址值、编译器,完全不可移植。

    所以如果你在某些环境看到 strlen(&p) 输出 3、0、6 甚至别的数,都不要惊讶。这不是“答案”,这是未定义行为在碰运气。


    六、面试笔试高频清单:这题到底在考什么?

    把上面四段代码压缩成一张清单,你复习时直接背这张:

  • sizeof 是运算符,strlen 是函数。

  • sizeof 只看类型,编译期求值;strlen 运行期找 '\\0'。

  • 数组名在大多数表达式里退化为首元素指针。

  • sizeof(arr) 不退化为指针,得到整个数组大小。

  • &arr 类型是 char (*)[N],&arr + 1 跳过整个数组。

  • &arr[0] 是首元素指针,&arr[0] + 1 跳过一个元素。

  • strlen 参数必须是合法的、以 '\\0' 结尾的字符串地址。

  • strlen(*arr)、strlen(arr[1])、strlen(*p) 都是把字符当地址,未定义行为。

  • 字符串字面量不可修改,建议写 const char* p = "abcdef";。

  • printf 打印 sizeof/strlen 返回值用 %zu,不要用 %d。

  • 函数参数里的 char arr[] 会退化成 char*,sizeof(arr) 得到指针大小。

  • 未定义行为不是“结果随机”,而是“标准不保证任何结果”,可能崩溃,也可能被优化成奇怪逻辑。


  • 七、为什么 2026 年还要学这种“老古董”?

    因为 C 语言的内存模型仍然是现代计算机的底座。

    当前计算机前沿里,内存安全是绝对热点。大量安全漏洞都和缓冲区溢出、越界读写、悬空指针、未定义行为有关。strlen(*arr) 这种代码在真实项目里一旦被触发,轻则崩溃,重则可能被利用。

    现在工业界常用这些工具来抓这类问题:

    • AddressSanitizer:抓越界、use-after-free

    • UndefinedBehaviorSanitizer:抓未定义行为

    • Valgrind:内存检测

    • Fuzzing:模糊测试

    • 静态分析:Coverity、Clang Static Analyzer、MISRA

    • Rust:用所有权和借用检查在编译期挡住大量内存错误

    • CHERI、指针 provenance:硬件和编译器层面追踪指针来源

    但注意,Rust 再火,C 仍然在操作系统、嵌入式、数据库、编译器、驱动里大量存在。你不可能只会“上层语言”,底层指针类型一塌糊涂。AI 编程助手能帮你写代码,但它也会生成 char* p = "abc"; 这种有隐患的代码。真正能兜底的人,还是得懂 sizeof 和 strlen 的区别。


    八、从教育学角度看:为什么你一看就会,一考就废?

    这里插一点学习方法,不是鸡汤,是认知科学。

    第一,先行组织者。先记“sizeof 看类型,strlen 找 \\0”,再做题。没有这个框架,细节越多越乱。

    第二,认知负荷控制。不要一上来就同时背 arr、&arr、p、&p。先分数组组,再分指针组。每组只变一个维度,这叫变式教学。

    第三,正例反例对照。strlen(arr) 是正例,strlen(*arr) 是反例。反例比正例更能让你记住边界。

    第四,最近发展区。你先会算 sizeof(arr),再学 sizeof(arr + 0),再学 sizeof(&arr + 1),每一步只比当前能力高一点,这叫脚手架。

    第五,形成性评价。不要只看答案,要自己先预测输出,再运行,再解释。预测错的地方,才是你真正的知识漏洞。

    第六,元认知。做完题问自己:我到底是在背答案,还是能解释为什么?如果面试官把 char 换成 int,我还会不会?


    九、练习题与针对性答案(C 语言实现)

    下面这些题,建议你先盖住答案,自己写输出。

    练习 1

    c

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

    int main() {
    char s[] = "hello";
    printf("%zu %zu\\n", sizeof(s), strlen(s));
    return 0;
    }

    答案:6 5

    解析:"hello" 是 5 个字符加 '\\0',数组大小 6;strlen 不算 '\\0',所以 5。


    练习 2

    c

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

    int main() {
    char s[10] = "hi";
    printf("%zu %zu\\n", sizeof(s), strlen(s));
    return 0;
    }

    答案:10 2

    解析:数组显式大小是 10;字符串内容是 hi,长度 2。剩余字节自动补 0。


    练习 3

    c

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

    int main() {
    char *s = "hello";
    printf("%zu %zu\\n", sizeof(s), strlen(s));
    return 0;
    }

    答案:64 位下 8 5

    解析:s 是指针,大小 8;strlen 数到 '\\0',长度 5。


    练习 4

    c

    #include <stdio.h>

    int main() {
    int a[5];
    printf("%zu %zu %zu\\n", sizeof(a), sizeof(a + 0), sizeof(*a));
    return 0;
    }

    答案:64 位下 20 8 4

    解析:a 是 int[5],5 × 4 = 20;a + 0 退化为 int*,指针 8;*a 是 int,4。


    练习 5

    c

    #include <stdio.h>

    int main() {
    char s[] = "abc";
    printf("%zu %zu %zu\\n", sizeof(&s), sizeof(&s + 1), sizeof(&s[0] + 1));
    return 0;
    }

    答案:64 位下 8 8 8

    解析:三个都是指针,大小都是 8。但步长不同:&s + 1 跳过整个数组,&s[0] + 1 跳过一个字符。


    练习 6

    c

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

    int main() {
    char s[] = "abc";
    printf("%zu\\n", strlen(&s[1]));
    return 0;
    }

    答案:2

    解析:从 'b' 开始,剩下 bc,长度 2。


    练习 7

    下面代码为什么危险?

    c

    #include <string.h>

    int main() {
    char *s = "abc";
    strlen(*s);
    return 0;
    }

    答案:*s 是字符 'a',不是地址。strlen 会把 'a' 的 ASCII 值当地址使用,导致未定义行为,通常段错误。


    练习 8:手写 my_strlen

    c

    #include <stdio.h>

    size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p != '\\0') {
    p++;
    }
    return (size_t)(p – s);
    }

    int main() {
    char s[] = "abcdef";
    printf("%zu\\n", my_strlen(s));
    return 0;
    }

    答案:输出 6。


    练习 9:写一个求数组元素个数的宏

    c

    #include <stdio.h>

    #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

    int main() {
    int a[5];
    printf("%zu\\n", ARRAY_SIZE(a));
    return 0;
    }

    答案:5

    注意:这个宏只在数组名可见的同一作用域有效。如果传进来的是指针,结果就错了。


    练习 10:函数参数里的数组

    c

    #include <stdio.h>

    void f(char arr[]) {
    printf("%zu\\n", sizeof(arr));
    }

    int main() {
    char s[7] = "abcdef";
    f(s);
    return 0;
    }

    答案:64 位下 8

    解析:函数参数中的 char arr[] 等价于 char *arr,数组名退化成指针,所以 sizeof(arr) 是指针大小,不是 7。


    十、最后再叮嘱几句

    这四段代码,真正要你掌握的不是死记输出,而是三件事:

  • 看类型:arr、&arr、&arr[0] 到底各是什么类型。

  • 看退化:数组名什么时候退化成指针。

  • 看边界:strlen 只接受合法字符串地址,传字符、传 char**、传越界地址都是未定义行为。

  • 如果面试官问你:“sizeof(arr) 和 sizeof(&arr) 有什么区别?”
    你就答:前者是整个数组大小,后者是指向数组的指针大小。
    如果问:“strlen(&p) 输出什么?”
    你就答:未定义行为,因为 &p 不是字符串地址,而是指针变量自己的地址。
    如果问:“为什么 strlen(*arr) 会崩?”
    你就答:*arr 是字符,不是地址,strlen 把 ASCII 码当地址用了。

    把这几句话吃透,这组题就不再是“混合双打”,而是你面试时的送分题。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » sizeof 和 strlen 到底差在哪?四段代码把 C 语言数组指针按在地上摩擦(附 64 位输出 + 面试题)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!