数组与指针
1.定义
指针:在K&R C上的原文为“A pointer is a group of cell (often two or four) that can hold an address.” 从概念上看,指针是包含地址的变量。
数组:能存储连续固定数量的相同类型元素的数据结构。
2.指针数组和数组指针
int *arr_for_p[5]; // 指针数组 int (*p_for_arr)[5]; // 数组指针
由于[]的优先级高于*,所以第一条语句可以写成这样
int* (arr_for_p[5]);
而第二条语句的解释可以理解为(*p_for_arr)代表了一个5个整数的数组,而p_for_arr就是这个数组的指针,也就是这个数组首地址,所以使用起来类似于(*p_for_arr)[i]。实际上p_for_arr和*p_for_arr是同一个值,因为指针变量指向的是数组的首地址,而指针变量取消引用之后得到的是这个数组(实质上也是数组的首地址),所以它们的值是一样的。
3.数组首地址与数组首元素的首地址
#include <stdio.h> int main(void) { int a[5]; int (*ptr_1)[5] = a; printf("ptr_1 = %p(%u)\n", ptr_1, ptr_1); int (*ptr_2)[5] = &a; printf("ptr_2 = %p(%u)\n", ptr_2, ptr_2); return 0; } /* 结果: ptr_1 = 0xbfc9a144(3217662276) ptr_2 = 0xbfc9a144(3217662276) */
从C的层面上理解a是数组首元素的首地址,而&a是数组的地址,很显然在栈上分配的数组是一段连续的内存,这里是20个字节,那么可以知道a和&a的值就是一样的——都是这段内存的起始地址,去掉打印语句我们得到的汇编代码如下
main: pushl %ebp movl %esp, %ebp subl $32, %esp leal -28(%ebp), %eax # a movl %eax, -8(%ebp) leal -28(%ebp), %eax # &a movl %eax, -4(%ebp) movl $0, %eax leave ret
在汇编层面上它们就是相同的值,都是%ebp-28。
4.数组指针运算
#include <stdio.h> int main(void) { int a[5]; int (*ptr_a)[3] = a; int (*ptr_b)[5] = a; int (*ptr_c)[10] = a; unsigned long tmp = a; printf("tmp = %p(%d)\n", tmp, tmp); tmp = ptr_a+1; printf("tmp = %p(%d)\n", tmp, tmp); tmp = ptr_b+1; printf("tmp = %p(%d)\n", tmp, tmp); tmp = ptr_c+1; printf("tmp = %p(%d)\n", tmp, tmp); return 0; } /* 结果: tmp = 0020FAA8(2161320) tmp = 0020FAB4(2161332) tmp = 0020FABC(2161340) tmp = 0020FAD0(2161360) */
可以看到三个数组的指针加1后的偏移都不同,分别是12,20和40,所以它们是以数组为单位偏移,而这些都是编译器的工作。
5.多维数组
本质上多维数组和一维数组并无二致,只是进行多次下标运算而已,看下面的代码
int arrs[10][10]; printf("arrs = %p\n", arrs); printf("arrs[0] = %p\n", arrs[0]); printf("&arrs[0][0] = %p\n", &arrs[0][0]); /* * 在Visual Studio 2010 中显示结果为: * arrs = 0031FBB4 * arrs[0] = 0031FBB4 * &arrs[0][0] = 0031FBB4 */
6.多级指针
多级指针本质上也是指针,也就是含有地址的变量,关键是这个地址要合法。
7.多级指针与多维数组
我个人认为这两者没有什么必然的联系,除非动态分配多维数组,不过始终记住多维数组下标运算编译器采用的是首地址偏移的方式,而多级指针下标运算采用的是指针多次解除引用的方式,二者完全不同。请看下面的代码
int main(void) { int arrs[3][4]; int** pparrs = (int **)arrs; arrs[2][2] = 33; pparrs[2][2] = 33; return 0; }
汇编代码如下
main: pushl %ebp movl %esp, %ebp subl $64, %esp leal -52(%ebp), %eax # 下面是直接偏移寻址 movl %eax, -4(%ebp) movl $33, -12(%ebp) movl -4(%ebp), %eax # 下面是取消多级引用 addl $8, %eax movl (%eax), %eax addl $8, %eax movl $33, (%eax) movl $0, %eax leave ret
当然上面的代码直接运行肯定会宕掉。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用