C语言指针访问二维数组元素的代码执行逻辑逐行解析
C语言指针与二维数组操作逐行解析
前置核心规则
- 数组退化规则:除了作为
sizeof、&操作的操作数时,数组名在表达式中会自动退化为指向其首元素的指针。一维数组int arr[2]的数组名arr等价于&arr[0],类型为int*;二维数组int arr[2][2]的数组名arr等价于&arr[0],类型为int (*)[2](指向长度为2的int数组的指针)。二维数组元素按行优先连续存储,行与行之间不存在内存空隙。 - 下标运算本质:
a[b]完全等价于*(a + b),地址偏移时会自动按照指针指向的类型长度计算步长:比如int* p指向地址0x100,p+1的实际地址是0x104(int占4字节),指向相邻的下一个int元素。 - 多重解引用规则:多层
*操作需要逐层跳转:**p首先取p中存储的地址,解引用得到第一层指针值,再对该指针值做解引用,才能拿到最终的int类型值。 - 恒等运算规则:
*&x完全等价于x,取地址和解引用操作直接抵消,不会产生额外运算。 - 指针数组识别:
int* values[2]是长度为2的数组,每个元素的类型是int*(int型指针),本质是存指针的数组,不是二维数组。
代码逐行执行拆解
先给出示例原始代码:
#include "stdio.h" int main() { int *pI, *values[2]; int i1[] = {1, 2}, i2[][2] = {{5, 10}, {20, 30}}; *values = i2[*i1]; values[1] = i1; pI = &i2[0][0]; printf(" %d %d\n", **values, *&values[1][1]); *pI += pI[i1[1]]; *values = pI; *(i1 + 1) *= *(values[1] + 1); printf(" %d %d\n", **values, *&values[1][1]); return 0; }
初始状态(变量定义完成后)
为方便推导,先明确内存布局(int占4字节,逻辑地址连续排列):
i1是长度为2的一维int数组:i1[0]=1、i1[1]=2i2是2行2列的二维int数组,内存连续排列:i2[0][0]=5、i2[0][1]=10、i2[1][0]=20、i2[1][1]=30values是长度为2的int指针数组,两个元素初始为未赋值的野指针pI是单个int指针,初始为未赋值的野指针
逐句执行逻辑
*values = i2[*i1];- 按优先级先计算
*i1:i1退化为&i1[0],解引用拿到i1[0]的值1 - 再计算
i2[1]:i2退化为指向首行的指针,偏移1个行长度拿到第二行子数组,子数组退化为指向第二行首元素的指针,即&i2[1][0](对应值20) - 最后计算
*values:values退化为指向自身首元素values[0]的指针,解引用就是values[0]本身 - 赋值结果:
values[0] = &i2[1][0],即values[0]指向i2第二行第一个元素20
- 按优先级先计算
values[1] = i1;- i1退化为
&i1[0],直接赋值给values数组第二个元素,values[1] = &i1[0],指向i1数组首元素1
- i1退化为
pI = &i2[0][0];- 取i2第一行第一个元素的地址赋值给pI,pI指向值为5的
i2[0][0]
- 取i2第一行第一个元素的地址赋值给pI,pI指向值为5的
- 第一个
printf输出:- 第一个参数
**values:第一次解引用values拿到values[0](即&i2[1][0]),第二次解引用拿到i2[1][0]的值20 - 第二个参数
*&values[1][1]:*&直接抵消,等价于values[1][1];values[1]是&i1[0],偏移1个int长度到&i1[1],解引用拿到i1[1]的值2 - 输出结果:
20 2
- 第一个参数
*pI += pI[i1[1]];- 先计算右值:
i1[1]的值为2,pI[2]等价于*(pI + 2),pI指向i2[0][0],偏移2个int长度到i2[1][0],拿到值20 - 左值
*pI是pI指向的i2[0][0],原值为5,执行+=20后,i2[0][0]被修改为25 - 此时i2内存更新为:
i2[0][0]=25、i2[0][1]=10、i2[1][0]=20、i2[1][1]=30
- 先计算右值:
*values = pI;*values等价于values[0],pI当前指向修改后的i2[0][0](值25),因此values[0] = &i2[0][0],不再指向之前的i2[1][0]
*(i1 + 1) *= *(values[1] + 1);- 左值
*(i1 + 1)等价于i1[1],当前值为2 - 右值
*(values[1] + 1):values[1]仍指向i1[0],偏移1个int长度到i1[1],解引用拿到值2 - 执行
*=运算:i1[1] = 2 * 2 = 4,i1数组更新为i1[0]=1、i1[1]=4
- 左值
- 第二个
printf输出:- 第一个参数
**values:第一次解引用拿到values[0](即&i2[0][0]),第二次解引用拿到值25 - 第二个参数
*&values[1][1]:等价于values[1][1],values[1]指向i1[0],偏移1位到i1[1],拿到修改后的值4 - 输出结果:
25 4
- 第一个参数
同类问题通用分析步骤
- 第一步先列初始内存表:把所有数组的元素、初始值、指针的初始指向写下来,不要靠记忆硬推
- 第二步拆解复杂表达式:按运算优先级从高到低计算,遇到数组名先判断是否退化为指针,遇到下标先转换成
*(地址+偏移)的形式,偏移量永远按指针指向的基类型计算步长 - 第三步逐句更新状态:每执行完一句赋值,立刻更新表中指针的指向、对应内存的存储值,不要沿用上一步的旧状态推导
- 遇到
*&连续操作直接划掉抵消,遇到多重指针逐层解引用,每解一次就替换成对应地址存储的值,直到拿到最终的基础类型值
内容的提问来源于stack exchange,提问作者Sylenthio
相关产品推荐
相关产品推荐

