深度解析运算符sizeof与函数strlen在数组与指针中的行为
在C语言中,sizeof 是编译期运算符,用来获取类型或对象所占用的内存字节数;而 strlen 是标准库函数,计算以 '\0' 结尾的字符串的长度(不含终止符)。虽然二者看似简单,但当操作数是数组名或指针时,结果往往出乎意料。本文通过多个场景的代码示例,帮助读者彻底理清其行为。
整型数组与sizeof
首先考察 sizeof 作用于整型数组时的表现。
#include <stdio.h>
int main(void) {
int nums[] = {10, 20, 30, 40};
printf("%zu\n", sizeof(nums)); /* 整个数组:4个int共16字节 */
printf("%zu\n", sizeof(nums + 0)); /* nums + 0 即 &nums[0],地址大小 */
printf("%zu\n", sizeof(*nums)); /* *nums 是首元素,int 大小 4 */
printf("%zu\n", sizeof(nums + 1)); /* 第二个元素的地址 */
printf("%zu\n", sizeof(nums[1])); /* 第二个元素值的大小,4 */
printf("%zu\n", sizeof(&nums)); /* 整个数组的地址,仍为地址 */
printf("%zu\n", sizeof(*&nums)); /* 等价于 sizeof(nums),16 */
printf("%zu\n", sizeof(&nums + 1)); /* 跨越整个数组后的地址 */
printf("%zu\n", sizeof(&nums[0] + 1)); /* 指向第二个元素的地址 */
return 0;
}
这里的核心规则是:当数组名单独出现在 sizeof 的操作数位置时,它代表整个数组,计算的是数组占用的总字节数。一旦写成 nums + 0、&nums 等形式,它便退化为首元素的指针(或数组指针),得到的是地址的大小,在32位平台上通常为4字节,64位平台上为8字节。
字符数组:sizeof与strlen的对照
字符数组的情况稍微复杂一些,因为既可以用 sizeof 查内存占用,也可以用 strlen 计算逻辑长度。
sizeof 视角
#include <stdio.h>
int main(void) {
char letters[] = {'a', 'b', 'c', 'd', 'e', 'f'};
printf("%zu\n", sizeof(letters)); /* 6 字节,整个数组 */
printf("%zu\n", sizeof(letters + 0)); /* 首元素地址大小 */
printf("%zu\n", sizeof(*letters)); /* 字符 'a' 的大小,1 */
printf("%zu\n", sizeof(letters[1])); /* 'b' 的大小,1 */
printf("%zu\n", sizeof(&letters)); /* 数组地址大小 */
printf("%zu\n", sizeof(&letters + 1)); /* 跳过数组后的地址 */
printf("%zu\n", sizeof(&letters[0] + 1)); /* 第二个元素地址 */
return 0;
}
这里的 sizeof 行为与整型数组完全一致,区别仅在于元素类型是 char,每个元素占用1字节。
strlen 视角
strlen 需要接收一个指向字符的指针,并计算从该地址开始直到遇到 '\0' 的字符个数。
#include <stdio.h>
#include <string.h>
int main(void) {
char letters[] = {'a', 'b', 'c', 'd', 'e', 'f'};
/* 因数组未包含'\0',以下调用结果均为随机值或导致错误 */
printf("%d\n", (int)strlen(letters)); /* 随机值:从首元素开始遇不到'\0' */
printf("%d\n", (int)strlen(letters + 0)); /* 同上 */
/* strlen(*letters) 或 strlen(letters[1]) 会将字符的 ASCII 码当地址,未定义行为 */
printf("%d\n", (int)strlen(&letters)); /* 随机值,类型为 char(*)[6],行为同首地址 */
printf("%d\n", (int)strlen(&letters + 1)); /* 从整个数组之后的地址开始,随机值 */
printf("%d\n", (int)strlen(&letters[0] + 1)); /* 从第二个元素开始,随机值 */
return 0;
}
值得注意的是,strlen(*letters) 或 strlen(letters[1]) 相当于 strlen('a'),即将字符值当作地址传入,属于严重错误,通常会导致程序崩溃。
字符串字面量初始化数组
如果用字符串字面量初始化字符数组,数组末尾会被自动追加 '\0',此时 sizeof 会计入该结束符,而 strlen 则不会。
#include <stdio.h>
#include <string.h>
int main(void) {
char str[] = "abcdef";
printf("sizeof: %zu\n", sizeof(str)); /* 7,包含 '\0' */
printf("sizeof(str+0): %zu\n", sizeof(str+0)); /* 地址大小 */
printf("sizeof(*str): %zu\n", sizeof(*str)); /* 1 */
printf("sizeof(str[1]): %zu\n", sizeof(str[1])); /* 1 */
printf("sizeof(&str): %zu\n", sizeof(&str)); /* 地址大小 */
printf("strlen(str): %d\n", (int)strlen(str)); /* 6,不包含 '\0' */
printf("strlen(str+0): %d\n", (int)strlen(str+0)); /* 6 */
printf("strlen(&str): %d\n", (int)strlen(&str)); /* 6,类型衰减为 char* */
printf("strlen(&str+1): %d\n", (int)strlen(&str+1)); /* 随机值,跳跃了包含'\0'的整个数组 */
printf("strlen(&str[0]+1): %d\n", (int)strlen(&str[0]+1)); /* 5,从第二个字符开始 */
return 0;
}
可以看到,sizeof(str) 为7,因为字符串末尾隐式含有一个 '\0'。而 strlen 则正确返回6。
指针指向字符串常量
当使用指针指向字符串字面量时,sizeof 计算的是指针变量自身的大小,而非字符串长度。
#include <stdio.h>
#include <string.h>
int main(void) {
const char *ptr = "abcdef";
printf("sizeof(ptr): %zu\n", sizeof(ptr)); /* 指针大小 */
printf("sizeof(ptr+1): %zu\n", sizeof(ptr+1)); /* 指针大小 */
printf("sizeof(*ptr): %zu\n", sizeof(*ptr)); /* 字符大小 1 */
printf("sizeof(ptr[0]): %zu\n", sizeof(ptr[0])); /* 1 */
printf("sizeof(&ptr): %zu\n", sizeof(&ptr)); /* 二级指针大小 */
printf("strlen(ptr): %d\n", (int)strlen(ptr)); /* 6 */
printf("strlen(ptr+1): %d\n", (int)strlen(ptr+1)); /* 5 */
/* strlen(*ptr) 等错误用法同样会导致未定义行为 */
printf("strlen(&ptr): %d\n", (int)strlen(&ptr)); /* 随机值,&ptr 是二级指针,遇'\0'未知 */
printf("strlen(&ptr[0]+1): %d\n", (int)strlen(&ptr[0]+1)); /* 5 */
return 0;
}
这里 ptr 本身是一个指针变量,sizeof(ptr) 返回的是指针宽度。而 strlen(ptr) 则通过该指针访问字符串内容,正确返回长度。将 &ptr 传入 strlen 会导致从二级指针变量内部开始扫描,结果不可预期。
二维数组中的sizeof
二维数组本质上是一维数组的数组,sizeof 的行为依然遵循数组名单独出现代表整个数组的规则。
#include <stdio.h>
int main(void) {
int matrix[3][4] = {0};
printf("%zu\n", sizeof(matrix)); /* 整个二维数组大小:3*4*4 = 48 */
printf("%zu\n", sizeof(matrix[0][0])); /* 单个元素大小 4 */
printf("%zu\n", sizeof(matrix[0])); /* 第一行(一个一维数组)大小:4*4=16 */
printf("%zu\n", sizeof(matrix[0] + 1)); /* matrix[0] 退化为 &matrix[0][0],+1 为第二个元素地址 */
printf("%zu\n", sizeof(*(matrix[0] + 1))); /* 解引用第二元素,int 大小 4 */
printf("%zu\n", sizeof(matrix + 1)); /* matrix 退化为指向第一行的指针,+1 指向第二行,地址大小 */
printf("%zu\n", sizeof(*(matrix + 1))); /* 解引用指向第二行的指针,得到第二行大小 16 */
printf("%zu\n", sizeof(&matrix[0] + 1)); /* 第二行的地址,地址大小 */
printf("%zu\n", sizeof(*(&matrix[0] + 1))); /* 第二行大小 16 */
printf("%zu\n", sizeof(*matrix)); /* matrix 不是单独操作数,退化为第一行地址,解引用为第一行大小 16 */
printf("%zu\n", sizeof(matrix[3])); /* 虽越界,但 sizeof 不实际访问内存,仍按类型推导出 16 */
return 0;
}
在二维数组中,matrix[0] 是第一行,其类型为 int[4],因此 sizeof(matrix[0]) 为16。而 matrix + 1 将指针移动到第二行,*(matrix + 1) 解引用后得到第二行的数组,大小仍为16。最后 matrix[3] 虽然逻辑上越界,但 sizeof 仅根据类型计算,不会引发运行时错误,结果与 matrix[0] 相同。
通过以上各种场景的分析,可以总结出两条关键原则:sizeof 在编译期根据操作数类型决定大小,数组名单独出现时才代表整个数组,否则退化为指针;strlen 则完全依赖于运行时的 '\0' 位置,传入非字符串地址会造成未定义行为。掌握这两点,便能在复杂代码中游刃有余地判断输出结果。
