C 编译器——Multidim 数组的间接寻址
Posted
技术标签:
【中文标题】C 编译器——Multidim 数组的间接寻址【英文标题】:C Compilers -- Indirection with Multidim Arrays 【发布时间】:2021-11-25 11:36:51 【问题描述】:根据定义,在每个 C 标准中,x[y] 等价于(并且经常编译为)*((x)+(y))。此外,数组的名称被转换为它的地址运算符——所以如果 x 是一个数组,它将是 *((&(x))+(y))
因此,对于多维数组,x 作为二维数组,x[y][z] 将等价于 (((&(x))+(y))+( z))
在我正在研究的小型玩具 C 编译器中,这无法生成正确的代码,因为它试图在每个 * 指令处间接访问指向的地址——这适用于单维数组,但适用于多维它会导致类似(在模糊的汇编伪代码中)
load &x; add y; deref; add z; deref
其中 deref 是在前一个计算的地址加载值的指令——因为这就是间接运算符的工作方式??
但是,这会产生错误的代码,因为我们应该处理一个地址,只在最后解除引用。我假设我缺少规范中的某些内容?
【问题讨论】:
"数组名转换为地址操作符" 不可以。你可以说x
被转换为&x[0]
,比较的类型不同到&x
。
数组在用作 L 值时不会转换为指针,仅用作 R 值。
deref
的作用取决于类型,您必须检测到这一点。一般来说,是的,deref() if simple pointer; then deref; if array; then only remove one dimension from type and don't change the value, if pointer to function, then do nothing
Aa还有&*
是空操作的情况,所以你必须检查下一个操作是否是&
然后什么都不做,例如。
【参考方案1】:
数组的名字被转换成一个地址操作符给它
没有。可以说x
转换为&x[0]
,与&x
相比具有不同的类型。
假设您有T a[M][N];
,则执行a[x][y]
会执行以下操作:
a
转换为T (*)[N]
类型的临时指针,指向第一个数组元素。
这个指针增加了x * sizeof(T[N])
,即x * N * sizeof(T)
。
指针被取消引用,给你一个T[N]
类型的值。
结果被转换为T *
类型的临时指针。
指针增加y * sizeof(T)
。
最后,指针被取消引用以产生T
类型的值。
请注意,数组本身(多维或非多维)不存储任何指向自身的指针。当转换为指针时,生成的指针是即时计算的。
【讨论】:
这给出了相同的结果?我认为我的问题可能更多在于间接运算符应该输出什么?它应该有更复杂的逻辑来检测这个吗?因为当 T(*)[N] 被取消引用时,它会将 T(*[N]) 视为一个指针并试图获取该地址的值? @PopeyeOtaku 您在问“为什么 2 个取消引用而不是 1 个”,答案是“因为有 2 个临时指针”。 “它应该有更复杂的逻辑吗” 不,这里没有复杂的逻辑,除了数组在传递给[]
时被隐式转换为指向其第一个元素的指针。
"当 T()[N] 被取消引用时,它将 T([N]) 视为指针并尝试获取该地址处的值" 取消引用的结果类型为T[N]
。当它被转换为指针时(在第二次添加之前),生成的指针是动态计算的,而不是从某个内存位置获取..【参考方案2】:
因此,对于多维数组,x 作为二维数组,x[y][z] 将等价于 (((&(x))+(y))+(z))
不,二维数组是数组的数组。所以*((x)+(y))
给了你那个数组,x
衰减成一个指向第一个元素的指针,然后取消引用它给你数组号y
。
这个数组也“衰减”成第一个元素的指针,所以你得到:
( (*((x)+(y))) + (z) )
当成为表达式的一部分时,数组总是衰减为指向它的第一个元素的指针。除了少数例外,即&
地址和sizeof
运算符。为什么像在伪代码中那样输入&
只是令人困惑。
一个实际的例子是:
int arr[x][y];
for(size_t i=0; i<x; i++)
for(size_t j=0; j<y; j++)
arr[i][j] = ...
在表达式arr[i][j]
中,[]
只是指针运算的“语法糖”(请参阅Do pointers support "array style indexing"?)。
所以我们得到*((arr)+(i))
,其中arr
被衰减为指向第一个元素类型的指针int(*)[y]
。
对该数组指针类型的指针算术产生数组编号i
,类型为int [y]
。
再说一次,这个有数组衰减,因为它也是表达式的数组部分。我们得到一个指向第一个元素的指针,输入int*
。
int*
+ j
的指针运算给出整数的地址,然后最终取消引用以给出实际的int
。
【讨论】:
【参考方案3】:所以,对于一个多维数组,x 作为一个二维数组,x[y][z] 相当于 (((&(x))+(y))+(z))
你错了。表达式 x[y][z] 的计算方式如下:
*( *( x + y ) + z )
这是一个演示程序:
#include <stdio.h>
int main(void)
enum M = 3, N = 3 ;
int a[M][N] =
1, 2, 3 ,
4, 5, 6 ,
7, 8, 9
;
for ( size_t i = 0; i < M; i++ )
for ( size_t j = 0; j < N; j++ )
printf( "%d ", *( *( a + i ) + j ) );
putchar( '\n' );
return 0;
它的输出是:
1 2 3
4 5 6
7 8 9
表达式中使用的数组指示符(极少数例外)被隐式转换为指向其第一个元素的指针。
所以如果你有一个数组声明如下:
int a[M][N];
然后数组指示符a
被转换为指向其第一个元素(“行”)的指针。数组元素的类型是 int[N]。所以指向此类对象的指针的类型为int ( * )[N]
。
如果您希望指针指向数组的i-th
元素,您需要编写表达式a + i
。取消对表达式的引用,您将获得第 i 行(一维数组),而在表达式中使用的第 i 行又被转换为指向其第一个元素的指针。
所以表达式a + i
的类型为int ( * )[N]
。
表达式*( a + i )
的类型为int[N]
,它立即隐式转换为int *
类型的指针,指向其封闭表达式中的第一个元素。
表达式*( a + i ) + j
指向二维数组“行”的j-th
元素。取消引用表达式*( *( a + i ) + j )
,您将获得数组i-th
行的j-th
元素。
【讨论】:
以上是关于C 编译器——Multidim 数组的间接寻址的主要内容,如果未能解决你的问题,请参考以下文章