C语言数组的内存布局与访问方式
本文介绍了 C 语言数组的内存布局与访问方式。内容涵盖一维、二维及多维数组的连续内存存储特性,详细解析了下标访问与指针访问的区别及指针运算规则。文章还阐述了数组名与指针的关系,区分了指针数组与数组指针,并指出了数组越界访问和内存泄漏等常见错误及其潜在风险,强调了正确管理内存的重要性。

本文介绍了 C 语言数组的内存布局与访问方式。内容涵盖一维、二维及多维数组的连续内存存储特性,详细解析了下标访问与指针访问的区别及指针运算规则。文章还阐述了数组名与指针的关系,区分了指针数组与数组指针,并指出了数组越界访问和内存泄漏等常见错误及其潜在风险,强调了正确管理内存的重要性。


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
数组是一种数据结构,用于存储相同类型的多个元素。数组中的元素在内存中连续存储,每个元素占用相同大小的内存空间。
一维数组的内存布局是连续的,数组的第一个元素存储在最低地址,最后一个元素存储在最高地址。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("数组第一个元素的地址:%p\n", &arr[0]);
printf("数组第二个元素的地址:%p\n", &arr[1]);
printf("数组第三个元素的地址:%p\n", &arr[2]);
printf("数组第四个元素的地址:%p\n", &arr[3]);
printf("数组第五个元素的地址:%p\n", &arr[4]);
return 0;
}
执行结果:
数组第一个元素的地址:0x7ffccb8a8a50
数组第二个元素的地址:0x7ffccb8a8a54
数组第三个元素的地址:0x7ffccb8a8a58
数组第四个元素的地址:0x7ffccb8a8a5c
数组第五个元素的地址:0x7ffccb8a8a60
分析:
每个 int 类型的元素占用 4 字节的内存空间,因此数组元素的地址依次递增 4 字节。
二维数组可以看作是一维数组的数组,内存布局也是连续的。例如,二维数组 int arr[2][3] 可以看作是两个一维数组,每个一维数组包含 3 个 int 类型的元素。
示例:
#include <stdio.h>
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("二维数组的地址:%p\n", arr);
printf("第一行数组的地址:%p\n", arr[0]);
printf("第二行数组的地址:%p\n", arr[1]);
printf("第一行第一个元素的地址:%p\n", &arr[0][0]);
printf("第一行第二个元素的地址:%p\n", &arr[0][1]);
printf("第一行第三个元素的地址:%p\n", &arr[0][2]);
printf("第二行第一个元素的地址:%p\n", &arr[1][0]);
printf("第二行第二个元素的地址:%p\n", &arr[1][1]);
printf("第二行第三个元素的地址:%p\n", &arr[1][2]);
return 0;
}
执行结果:
二维数组的地址:0x7fffd9a8a6a0
第一行数组的地址:0x7fffd9a8a6a0
第二行数组的地址:0x7fffd9a8a6ac
第一行第一个元素的地址:0x7fffd9a8a6a0
第一行第二个元素的地址:0x7fffd9a8a6a4
第一行第三个元素的地址:0x7fffd9a8a6a8
第二行第一个元素的地址:0x7fffd9a8a6ac
第二行第二个元素的地址:0x7fffd9a8a6b0
第二行第三个元素的地址:0x7fffd9a8a6b4
分析:
二维数组的内存布局是连续的,第一行数组的地址与二维数组的地址相同,第二行数组的地址比第一行数组的地址高 12 字节(3 个 int 元素,每个 4 字节)。
多维数组的内存布局可以看作是一维数组的嵌套,内存空间连续存储。例如,三维数组 int arr[2][3][4] 可以看作是两个二维数组,每个二维数组包含 3 个一维数组,每个一维数组包含 4 个 int 类型的元素。
数组元素可以通过数组下标访问,数组下标的范围是从 0 到数组长度减 1。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("arr[0] = %d\n", arr[0]);
printf("arr[1] = %d\n", arr[1]);
printf("arr[2] = %d\n", arr[2]);
printf("arr[3] = %d\n", arr[3]);
printf("arr[4] = %d\n", arr[4]);
return 0;
}
执行结果:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
数组名可以看作是数组第一个元素的指针,因此可以通过指针访问数组元素。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
printf("ptr[0] = %d\n", ptr[0]);
printf("ptr[1] = %d\n", ptr[1]);
printf("ptr[2] = %d\n", ptr[2]);
printf("ptr[3] = %d\n", ptr[3]);
printf("ptr[4] = %d\n", ptr[4]);
return 0;
}
执行结果:
ptr[0] = 1
ptr[1] = 2
ptr[2] = 3
ptr[3] = 4
ptr[4] = 5
指针可以进行运算,如加法、减法、比较等。指针运算的结果与指针所指向的类型有关。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
printf("ptr 指向的地址:%p\n", ptr);
printf("ptr+1 指向的地址:%p\n", ptr + 1);
printf("ptr+2 指向的地址:%p\n", ptr + 2);
return 0;
}
执行结果:
ptr 指向的地址:0x7fffd9a8a6a0
ptr+1 指向的地址:0x7fffd9a8a6a4
ptr+2 指向的地址:0x7fffd9a8a6a8
分析:
指针每次加 1,地址递增 4 字节(int 类型的大小)。
数组名可以看作是数组第一个元素的指针,但数组名是常量指针,不能修改。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("数组名 arr:%p\n", arr);
printf("数组第一个元素的地址:%p\n", &arr[0]);
return 0;
}
执行结果:
数组名 arr:0x7fffd9a8a6a0
数组第一个元素的地址:0x7fffd9a8a6a0
分析:
数组名 arr 和数组第一个元素的地址 &arr[0] 是相同的。
指针数组和数组指针是两种不同的数据类型:
示例:
#include <stdio.h>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {6, 7, 8, 9, 10};
// 指针数组
int* ptr_arr[2] = {arr1, arr2};
printf("ptr_arr[0][0] = %d\n", ptr_arr[0][0]);
printf("ptr_arr[0][1] = %d\n", ptr_arr[0][1]);
printf("ptr_arr[1][0] = %d\n", ptr_arr[1][0]);
printf("ptr_arr[1][1] = %d\n", ptr_arr[1][1]);
// 数组指针
int (*arr_ptr)[5] = arr1;
printf("arr_ptr[0][0] = %d\n", arr_ptr[0][0]);
printf("arr_ptr[0][1] = %d\n", arr_ptr[0][1]);
arr_ptr++;
printf("arr_ptr[0][0] = %d\n", arr_ptr[0][0]);
printf("arr_ptr[0][1] = %d\n", arr_ptr[0][1]);
return 0;
}
执行结果:
ptr_arr[0][0] = 1
ptr_arr[0][1] = 2
ptr_arr[1][0] = 6
ptr_arr[1][1] = 7
arr_ptr[0][0] = 1
arr_ptr[0][1] = 2
arr_ptr[0][0] = 6
arr_ptr[0][1] = 7
分析:
ptr_arr 包含两个指针,分别指向 arr1 和 arr2。arr_ptr 指向一个包含 5 个 int 元素的数组,arr_ptr++ 会指向 arr2。数组下标越界是指访问数组元素时,下标超过了数组的范围。数组下标越界会导致未定义行为,可能会读取到内存中的随机值,或者导致程序崩溃。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("arr[5] = %d\n", arr[5]);
return 0;
}
执行结果:
arr[5] = 0
分析:
数组 arr 的下标范围是 0 到 4,访问 arr[5] 会导致数组下标越界,程序会读取到内存中的随机值。
指针越界访问是指指针指向的地址超出了数组的范围。指针越界访问会导致未定义行为,可能会读取到内存中的随机值,或者导致程序崩溃。
示例:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
printf("ptr[5] = %d\n", ptr[5]);
return 0;
}
执行结果:
ptr[5] = 0
分析:
指针 ptr 指向数组 arr 的第一个元素,ptr[5] 会指向数组 arr 的第六个元素,导致指针越界访问。
内存泄漏是指程序在动态分配内存后,没有及时释放内存,导致内存无法再使用。数组的内存泄漏通常发生在使用动态数组时。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int* arr = (int*)malloc(5 * sizeof(int));
arr[0] = 1; arr[1] = 2; arr[2] = 3; arr[3] = 4; arr[4] = 5;
// 没有释放内存
return 0;
}
分析:
程序使用 malloc 函数动态分配了 5 个 int 元素的内存,但没有使用 free 函数释放内存,导致内存泄漏。
通过本文的学习,我们掌握了 C 语言数组的内存布局与访问方式,包括一维数组、二维数组和多维数组的内存布局,以及数组元素的访问方法(数组下标访问和指针访问)。我们还学习了指针与数组的关系,以及数组访问不当导致的错误(数组下标越界、指针越界访问和内存泄漏)。
在编写代码时,我们应该避免数组访问不当导致的错误,使用正确的访问方式,并及时释放动态分配的内存。同时,我们应该掌握指针与数组的关系,以便更好地使用数组和指针。