在上一讲,我们详细分析了宏定义中的优先级与副作用陷阱。今天进入 Day 18,聚焦于 sizeof操作符的应用与陷阱。这是C语言开发中极易被忽视但又影响极大的基础点,涉及类型安全、内存分配、数组与指针的本质区别等核心话题。
1. 原理与细节逐步讲解
1.1 sizeof的基本原理
sizeof
是一个编译时运算符,用于返回对象或类型所占的字节数(以size_t
类型返回)。- 它可以作用于类型(如
sizeof(int)
)、变量(如sizeof x
)、表达式(如sizeof(x+1)
)。 - 注意:
sizeof
并不对表达式求值,只关心其类型和大小。
1.2 应用场景
- 结构体、数组、指针大小确定。
- 为动态分配内存(如
malloc
)时计算字节数。 - 类型安全的内存拷贝(如
memcpy
)。
1.3 与数组、指针的关系
- 数组名在大多数表达式中会退化为指针,但在
sizeof
、&
、_Alignof
等运算符下不会退化。 sizeof(array)
返回整个数组的大小(元素数 × 单元素大小)。sizeof(pointer)
返回指针本身的大小(通常是4字节或8字节,视平台定)。
2. 典型陷阱/缺陷说明及成因剖析
2.1 指针与数组混用
int arr[10];
int *p = arr;
printf("%zu %zu\n", sizeof(arr), sizeof(p));
sizeof(arr)
== 40(假设int
为4字节),sizeof(p)
== 8(64位平台),
误用可导致内存分配、拷贝等逻辑大错特错。
2.2 作为函数参数时的退化
void foo(int arr[10]) {
printf("%zu\n", sizeof(arr));
}
输出的是指针大小而非数组大小,因为函数参数int arr[10]
实际等价于int *arr
。
2.3 结构体对齐与填充
struct S { char c; int i; };
printf("%zu\n", sizeof(struct S));
实际大小通常大于成员之和,因为编译器会对齐填充。
2.4 sizeof对表达式不求值
int x = 1;
printf("%zu\n", sizeof(x++)); // x不会自增
3. 规避方法与最佳设计实践
- 动态分配内存时,用变量而非类型作为sizeof参数,如
malloc(n * sizeof(*ptr))
。 - 不要在函数参数中用
sizeof(arr)
判断数组长度,应显式传递数组长度。 - 结构体拷贝、内存操作等用
sizeof(变量)
而非sizeof(类型)
,防止类型变更时遗漏。 - 明确区分数组与指针,避免混用导致sizeof结果错误。
4. 典型错误代码与优化后正确代码对比
错误代码
void foo(int arr[10]) {
size_t n = sizeof(arr) / sizeof(arr[0]); // 错误!arr是指针
for (size_t i = 0; i < n; ++i) {
arr[i] = 0;
}
}
正确代码
void foo(int arr[], size_t n) {
for (size_t i = 0; i < n; ++i) {
arr[i] = 0;
}
}
// 调用时
int data[10];
foo(data, sizeof(data)/sizeof(data[0]));
机制分析
- 错误代码中,
sizeof(arr)
其实是指针大小,导致n==1或2,循环次数错误。 - 正确代码将数组长度作为参数传递,保证了逻辑正确性。
5. 底层原理补充
sizeof
是编译期求值,不会真的执行表达式。- 数组参数退化为指针,是C语言参数传递机制决定的。
- 指针和数组的本质区别决定了sizeof的不同表现。
- 结构体的sizeof取决于“内存对齐填充”规则(详见Day 4)。
6. SVG图示:数组与指针的sizeof对比
7. 总结与实际建议
sizeof
是C语言类型和内存操作的基石,但指针与数组、结构体对齐、表达式不求值等陷阱极多。- 编写涉及内存分配、数组操作、结构体拷贝等代码时,一定要明确
sizeof
操作的对象和实际含义。 - 最佳实践:用变量而非类型做
sizeof
参数,数组传参时要显式传递长度,避免“指针退化”带来的悲剧。
牢记:搞清楚sizeof
的对象、时机和本质,是C语言安全与可移植代码的基础!