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 数组的间接寻址的主要内容,如果未能解决你的问题,请参考以下文章

间接寻址和数组

C++ 指向指针的指针

《数据结构》之数组结构和链表

keil中 如何向指定地址写入数据

不使用 eval 的间接寻址

C#10 可空模式:如何告诉编译器我在构造函数中间接设置了不可空属性?