C语言数组与sizeof操作符详解
核心概念:数组名的"退化"规则
基本规则
- 数组名在大多数表达式中会自动转换为指向其第一个元素的指针
- 例外情况(数组名不退化为指针):
- 使用
sizeof运算符时 - 使用取地址运算符
&时
- 使用
sizeof不同用法的结果对比
示例数组定义
int arr[20]; // 包含20个整数的数组
// 假设:sizeof(int) = 4字节,指针大小为8字节(64位系统)
详细对比表
| 表达式 | arr的类型 | sizeof结果 | 解释说明 |
|---|---|---|---|
sizeof arr |
int [20] |
80字节 | 计算整个数组的大小:20 × 4 = 80字节 |
sizeof (arr + 1) |
int* |
8字节 | 计算指针的大小,arr退化为指针 |
sizeof arr[0] |
int |
4字节 | 计算数组单个元素的大小 |
sizeof &arr |
int (*)[20] |
8字节 | 计算数组指针的大小 |
sizeof *arr |
int |
4字节 | arr退化为指针,解引用得到int |
验证代码示例
#include <stdio.h>
int main() {
int arr[20];
printf("=== sizeof 结果对比 ===\n");
printf("sizeof arr = %2zu (整个数组大小)\n", sizeof arr);
printf("sizeof (arr + 1) = %2zu (指针大小)\n", sizeof(arr + 1));
printf("sizeof arr[0] = %2zu (int大小)\n", sizeof arr[0]);
printf("sizeof &arr = %2zu (指针大小)\n", sizeof &arr);
printf("\n=== 地址验证 ===\n");
printf("arr = %p\n", (void*)arr);
printf("arr + 1 = %p (前进4字节,一个int大小)\n", (void*)(arr + 1));
printf("&arr = %p (与arr值相同)\n", (void*)&arr);
printf("&arr + 1 = %p (前进80字节,整个数组大小)\n", (void*)(&arr + 1));
return 0;
}
关键记忆点
1. 数组名在不同上下文中的含义
arr // 在sizeof中:整个数组
// 在其他表达式中:指向第一个元素的指针
arr + 1 // 总是:指向第二个元素的指针
&arr // 总是:指向整个数组的指针
2. 指针运算的差异
arr + 1 // 地址增加 sizeof(int) = 4字节
&arr + 1 // 地址增加 sizeof(arr) = 80字节
3. 实用判断方法
- 如果
sizeof直接作用于数组名 → 得到整个数组大小 - 如果数组名出现在表达式中 → 通常退化为指针
&运算符作用于数组名 → 得到指向数组的指针
常见误区提醒
❌ 错误理解:认为 arr总是指针
✅ 正确理解:arr是数组,在特定情况下退化为指针
❌ 错误理解:sizeof(arr+1)计算第二个元素的大小
✅ 正确理解:sizeof(arr+1)计算指针本身的大小
这个笔记总结了数组与sizeof的核心知识点,建议结合代码实践来加深理解。
C语言数组的退化机制详解
什么是数组退化?
在C语言中,数组退化(Array Decay)是指数组在特定情况下会自动转换为指向其首元素的指针。这是C语言中一个重要且容易混淆的特性。
数组退化的发生场景
- 函数参数传递
当数组作为函数参数传递时,会发生退化:
#include <stdio.h>
// 函数声明中的数组参数实际上是指针
void printArray(int arr[], int size) {
// 这里的arr实际上是指向int的指针
printf("Sizeof arr in function: %zu bytes\n", sizeof(arr)); // 输出指针大小
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
printf("Sizeof myArray in main: %zu bytes\n", sizeof(myArray)); // 输出20字节
// 传递数组给函数时发生退化
printArray(myArray, 5);
return 0;
}
输出:
Sizeof myArray in main: 20 bytes
Sizeof arr in function: 8 bytes
1 2 3 4 5
- 数组赋值给指针变量
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// 数组退化发生:arr退化为指向首元素的指针
int *ptr = arr;
// 以下两种访问方式是等价的
printf("arr[2] = %d\n", arr[2]); // 输出:3
printf("ptr[2] = %d\n", ptr[2]); // 输出:3
printf("*(arr+2) = %d\n", *(arr+2)); // 输出:3
return 0;
}
- 数组在表达式中的使用
int main() {
int arr[3] = {10, 20, 30};
// 在表达式中,数组名退化为指针
int *p1 = arr + 1; // 指向第二个元素
int *p2 = &arr[0] + 1; // 等价写法
printf("*p1 = %d\n", *p1); // 输出:20
printf("*p2 = %d\n", *p2); // 输出:20
return 0;
}
不会发生退化的场景
- sizeof运算符
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// sizeof不会导致数组退化
printf("Size of array: %zu\n", sizeof(arr)); // 输出:20(5 * 4)
// 与指针大小对比
int *ptr = arr;
printf("Size of pointer: %zu\n", sizeof(ptr)); // 输出:8(64位系统)
return 0;
}
- &运算符
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// &arr不会退化,得到的是指向整个数组的指针
int (*ptr_to_array)[5] = &arr;
printf("Address of arr: %p\n", (void*)arr);
printf("Address of &arr: %p\n", (void*)&arr);
// 两个地址值相同,但类型不同
// 指针运算的差异
printf("arr + 1: %p\n", (void*)(arr + 1)); // 前进4字节
printf("&arr + 1: %p\n", (void*)(&arr + 1)); // 前进20字节
return 0;
}
- 字符串字面量的初始化
int main() {
// 字符串字面量初始化字符数组时不会退化
char str[] = "Hello"; // 创建了一个字符数组
// 这与指针不同
char *ptr = "Hello"; // 指针指向字符串字面量
printf("Sizeof str: %zu\n", sizeof(str)); // 输出:6(包含'\0')
printf("Sizeof ptr: %zu\n", sizeof(ptr)); // 输出:8
return 0;
}
多维数组的退化
多维数组的退化更为复杂:
#include <stdio.h>
void print2DArray(int arr[][3], int rows) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 第一维退化,第二维保持
print2DArray(matrix, 2);
// 查看大小差异
printf("Sizeof matrix: %zu\n", sizeof(matrix)); // 输出:24
printf("Sizeof matrix[0]: %zu\n", sizeof(matrix[0])); // 输出:12
// 指针类型示例
int (*ptr_to_array)[3] = matrix; // 指向包含3个int的数组的指针
return 0;
}
避免问题的实用技巧
- 使用结构体包装数组
typedef struct {
int data[5];
size_t size;
} ArrayWrapper;
void processArray(ArrayWrapper wrapper) {
// 这里不会发生退化
printf("Size in function: %zu\n", sizeof(wrapper.data));
}
int main() {
ArrayWrapper wrapper = {{1, 2, 3, 4, 5}, 5};
processArray(wrapper);
return 0;
}
- 明确传递数组大小
void safeArrayProcessing(int *arr, size_t size) {
for(size_t i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
size_t size = sizeof(arr) / sizeof(arr[0]);
safeArrayProcessing(arr, size);
return 0;
}
总结
· 数组退化是C语言中将数组自动转换为指针的机制
· 主要发生在:函数参数传递、赋值给指针、表达式使用
· 不会发生在:sizeof、&运算符、字符串初始化字符数组
· 理解退化机制对于避免bug和编写正确的C代码至关重要
· 使用适当的技术(如结构体包装或显式传递大小)可以避免退化带来的问题
理解数组退化机制有助于编写更安全、更可预测的C代码,特别是在处理数组和指针时。