c语言数组是一种构造数据类型,它可以存储多个同类型的元素,其核心存储逻辑是在内存中申请一块连续的存储空间,所有元素按照顺序依次排列在这块空间中,每个元素的大小由数组的类型决定,比如int类型数组每个元素占4字节,char类型数组每个元素占1字节。

一维数组的存储方式
一维数组是最基础的数组形式,声明时会指定元素类型和元素个数,编译器会根据这两个信息计算需要的总内存空间,然后在栈区或者全局数据区分配对应的连续内存。
比如声明一个包含3个int元素的数组,总内存大小就是3乘以int类型的字节数,在常见的编译环境中int占4字节,那么总空间就是12字节。数组的首地址就是第一个元素的地址,后续元素的地址依次递增,递增的步长就是单个元素的字节数。
我们可以通过下面的代码来验证一维数组的存储地址规律:
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
// 打印数组首地址和每个元素的地址
printf("数组首地址: %pn", arr);
printf("arr[0]地址: %pn", &arr[0]);
printf("arr[1]地址: %pn", &arr[1]);
printf("arr[2]地址: %pn", &arr[2]);
// 打印每个元素的存储值
printf("arr[0] = %dn", arr[0]);
printf("arr[1] = %dn", arr[1]);
printf("arr[2] = %dn", arr[2]);
return 0;
}
运行这段代码可以看到,arr[1]的地址比arr[0]大4,arr[2]的地址比arr[1]大4,正好符合int类型元素占4字节的规律,说明元素确实是连续存储的。
二维数组的存储方式
二维数组可以看作是一维数组的延伸,本质依然是连续存储的,只是逻辑上被分成了多行多列。二维数组的存储顺序是按行优先的,也就是先存储第一行的所有元素,再存储第二行的所有元素,依次类推。
比如声明一个2行3列的int二维数组,存储顺序就是先存第一行的3个元素,再存第二行的3个元素,整个数组的内存依然是连续的,总大小是行数乘以列数乘以单个元素的字节数。
下面的代码可以验证二维数组的存储连续性:
#include <stdio.h>
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 遍历打印所有元素的地址和值
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("arr[%d][%d]地址: %p, 值: %dn", i, j, &arr[i][j], arr[i][j]);
}
}
return 0;
}
运行后会发现所有元素的地址都是依次递增4字节的,即使是从第一行的最后一个元素到第二行的第一个元素,地址也是连续的,没有断开,这就证明了二维数组在内存中也是连续存储的。
数组存储的注意事项
- 数组在声明时必须指定大小(除了作为函数参数或者初始化时省略大小的场景),编译器需要提前知道要分配多少连续内存。
- 数组的下标是从0开始的,访问数组元素时如果下标超出声明的范围,会访问到不属于该数组的内存空间,可能导致程序出错。
- 数组名在大多数场景下代表数组的首地址,对数组名取地址得到的也是数组的首地址,但是数组名和指针还是有区别,数组名是一个不可修改的常量,不能进行自增自减操作。
- 如果是全局数组或者静态数组,存储位置在数据区,生命周期是整个程序运行期间;如果是局部数组,存储位置在栈区,生命周期是所在函数的执行期间,函数执行结束栈区内存会被释放。
数组初始化时的存储
数组初始化时,如果给出的初始化值少于数组声明的元素个数,剩余的元素会被自动初始化为0(全局数组和静态数组)或者不确定值(局部数组未显式初始化的情况)。如果初始化时省略数组大小,编译器会根据初始化值的个数自动确定数组的大小。
比如下面的代码:
#include <stdio.h>
int main() {
// 声明大小为5的数组,只初始化前3个元素
int arr[5] = {1, 2, 3};
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %dn", i, arr[i]);
}
// 省略大小的数组,编译器自动确定大小为3
int arr2[] = {10, 20, 30};
printf("arr2的大小是%d个元素n", sizeof(arr2)/sizeof(arr2[0]));
return 0;
}
运行后会发现arr[3]和arr[4]的值都是0,而arr2的大小被自动确定为3个元素,这些都是数组存储时的常见特性。