数组的存储模型
数组在内存中以连续的方式存储,所有元素紧密排列。数组名在大多数表达式中会退化为指向首元素的指针,但数组名本身不是可修改的左值。理解数组的存储模型和数组名与指针的关系,是掌握 C 语言数组和指针操作的基础。
连续内存布局
数组的元素在内存中连续存放:
int arr[5] = {10, 20, 30, 40, 50};
/* 内存布局(假设 int 为 4 字节):
地址 值
0x1000 10 ← arr[0]
0x1004 20 ← arr[1]
0x1008 30 ← arr[2]
0x100C 40 ← arr[3]
0x1010 50 ← arr[4]
*/
元素地址 = 基地址 + 索引 × 元素大小
/* arr[i] 的地址 = arr + i * sizeof(int) */
/* arr[2] 的地址 = 0x1000 + 2 * 4 = 0x1008 */
数组名与指针
数组名在大多数表达式中退化为指向首元素的指针:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; /* arr 退化为 &arr[0] */
printf("%d\n", *p); /* 10 */
printf("%d\n", *(p + 1)); /* 20 */
/* 等价访问 */
printf("%d\n", arr[2]); /* 30 */
printf("%d\n", *(arr + 2)); /* 30 */
printf("%d\n", 2[arr]); /* 30 —— arr[2] 等价于 *(arr+2) 等价于 *(2+arr) 等价于 2[arr] */
arr[i] 和 *(arr + i) 完全等价,这是 C 语言数组访问的本质。
数组名不是指针
数组名在某些情况下不是指针:
sizeof 数组:
int arr[5];
printf("%zu\n", sizeof(arr)); /* 20(5 * 4) */
printf("%zu\n", sizeof(int *)); /* 4 或 8 */
sizeof(arr) 返回整个数组的大小,不是指针大小。
取地址 &arr:
int arr[5];
int *p1 = arr; /* 指向 int,&arr[0] */
int (*p2)[5] = &arr; /* 指向 int[5] 数组 */
printf("%p\n", (void *)p1); /* 0x1000 */
printf("%p\n", (void *)p2); /* 0x1000 */
printf("%p\n", (void *)(p1 + 1)); /* 0x1004 */
printf("%p\n", (void *)(p2 + 1)); /* 0x1014(跳过整个数组) */
arr 和 &arr 的地址值相同,但类型不同:arr 是 int*,&arr 是 int (*)[5]。
数组初始化:
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
/* arr2 = arr1; */ /* 错误:数组名不是可修改的左值 */
/* 正确:逐个复制 */
for (int i = 0; i < 5; i++)
arr2[i] = arr1[i];
/* 或用 memcpy */
#include <string.h>
memcpy(arr2, arr1, sizeof(arr1));
数组名不是可修改的左值
数组名不能被赋值,不能自增自减:
int arr[5];
/* arr = NULL; */ /* 错误 */
/* arr++; */ /* 错误 */
/* &arr++; */ /* 错误 */
但指向数组元素的指针可以:
int *p = arr;
p++; /* 合法:p 指向 arr[1] */
数组与指针的区别
| 特性 | 数组 | 指针 |
|---|---|---|
| 内存分配 | 编译器分配 | 程序员分配(或指向已有对象) |
| sizeof | 数组总大小 | 指针大小 |
| 取地址 | &arr 是数组指针 | &p 是指针的指针 |
| 赋值 | 不可赋值 | 可赋值 |
| 初始化 | int arr[] = {1,2,3}; | int *p = arr; |
char str[] = "Hello"; /* 数组:6 字节内存,内容可修改 */
char *ptr = "Hello"; /* 指针:指向字符串常量,内容不可修改 */
str[0] = 'h'; /* 合法 */
/* ptr[0] = 'h'; */ /* 未定义行为!字符串常量通常只读 */
常见错误
数组赋值:
int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a; /* 错误:数组不可赋值 */
/* 正确 */
memcpy(b, a, sizeof(a));
sizeof 在函数参数中:
void func(int arr[])
{
int n = sizeof(arr) / sizeof(arr[0]); /* 错误!sizeof(arr) = sizeof(int*) */
}
/* 正确:显式传递大小 */
void func(int arr[], int n)
数组名自增:
int arr[5];
arr++; /* 错误:数组名不是可修改的左值 */
int *p = arr;
p++; /* 合法 */
最佳实践
- 理解数组名在表达式中的退化规则
- 用
sizeof(arr) / sizeof(arr[0])计算数组大小(只在声明作用域内) - 数组作为函数参数时,始终传递大小
- 需要修改数组内容时,用数组而非指针指向字符串常量
- 用
memcpy或循环复制数组内容 - 区分
arr(int*)和&arr(int (*)[N])的类型差异