C 编译器如何知道如何存储二维数组的地址?

Tro*_*lta 1 c gcc

考虑您典型的 gcc 编译器(C99 模式)并考虑一个数组:

char array[2][4];
Run Code Online (Sandbox Code Playgroud)

显然编译器会编译(我假设它在翻译过程中?)使目标机器(假设它是 X86)以它“解决”的方式工作的代码,或者我们可以说,“分配”两个地址array[0][0] 和 array[1][0] 在访问其中任何一个之前(我可能完全错了)。我的问题是编译器如何“知道”这一点,因为它只是一个愚蠢的程序?它是某种简单的递归算法,做得非常正确,所以我们真的不必关心有多少维(如“哦,名称“数组”后面有一个括号对?我只是翻译它进入一个地址,等待,有2个?地址然后)或那些设计编译器的人专门研究了这种情况并编写了编译器来解决它?

如果您对我的问题感到困惑,请考虑一维数组 arr[2]。我可以让 arr 参与各种计算,知道它只是一个地址,可以说是“起始地址”。但是对于一维数组,您只需要一个“起始地址”,这在编译期间很容易完成,因为编译器只会将该名称(在本例中为 arr)转换为未初始化的地址(同样,我可能完全错了),但是对于一个二维数组,编译器需要处理多个地址,它是如何工作的?

汇编代码会是什么样子?

Ric*_*ard 5

一个二维数组,例如

int arr[3][2] = {{0, 1}, {2, 3}, {4, 5}};
Run Code Online (Sandbox Code Playgroud)

在内存中排列为:

0 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

因为 C 是一种行主语言。

这与以下布局相同:

int arrflat[6] = { 0, 1, 2, 3, 4, 5 };
Run Code Online (Sandbox Code Playgroud)

并且您可以仅使用它们的地址(分别为arrarrflat)来访问和操作这两个数组。

但是,当您通过arr[y][x]或访问元素arrflat[i]时会发生转换。

arrflat[i] 变成

arrflat+i
Run Code Online (Sandbox Code Playgroud)

arr[y][x]变成

arr+(y*width+x)
Run Code Online (Sandbox Code Playgroud)

事实上,你可以arr用这种方式进行指针数学运算。

一个简单的测试程序是:

#include <stdio.h>

int main(){
  int arr[3][2]  = {{0, 1}, {2, 3}, {4, 5}};
  int arrflat[6] = { 0, 1, 2, 3, 4, 5 };

  for(int y=0;y<3;y++)
  for(int x=0;x<2;x++)
    printf("%d\n",arr[y][x]);

  for(int i=0;i<6;i++)
    printf("%d\n",arrflat[i]);
}
Run Code Online (Sandbox Code Playgroud)

编译它以生成程序集

gcc -g -Wa,-adhls  test.c
Run Code Online (Sandbox Code Playgroud)

(缩写)输出是:

 9:test.c        ****     printf("%d\n",arr[y][x]);
49                  .loc 1 9 0 discriminator 3
50 007d 8B45B8      movl  -72(%rbp), %eax
51 0080 4898        cltq
52 0082 8B55B4      movl  -76(%rbp), %edx
53 0085 4863D2      movslq  %edx, %rdx
54 0088 4801D2      addq  %rdx, %rdx
55 008b 4801D0      addq  %rdx, %rax
56 008e 8B4485C0    movl  -64(%rbp,%rax,4), %eax
57 0092 89C6        movl  %eax, %esi
58 0094 BF000000    movl  $.LC0, %edi
58      00
59 0099 B8000000    movl  $0, %eax
59      00
60 009e E8000000    call  printf

12:test.c        ****     printf("%d\n",arrflat[i]);
80                  .loc 1 12 0 discriminator 3
81 00c0 8B45BC      movl  -68(%rbp), %eax
82 00c3 4898        cltq
83 00c5 8B4485E0    movl  -32(%rbp,%rax,4), %eax
84 00c9 89C6        movl  %eax, %esi
85 00cb BF000000    movl  $.LC0, %edi
85      00
86 00d0 B8000000    movl  $0, %eax
86      00
87 00d5 E8000000    call  printf
Run Code Online (Sandbox Code Playgroud)

消除两次调用printf和注释之间的公共代码给出:

 9:test.c        ****     printf("%d\n",arr[y][x]);
49                  .loc 1 9 0 discriminator 3
50 007d 8B45B8      movl  -72(%rbp), %eax        #Load address -72 bytes from the memory pointed to by %rbp
51 0080 4898        cltq                         #Turn this into a 64-bit integer address (where is `arr`?)
52 0082 8B55B4      movl  -76(%rbp), %edx        #Load address -76 bytes from the memory pointed to by %rbp
53 0085 4863D2      movslq  %edx, %rdx           #Turn %edx into a signed 64-bit offset
54 0088 4801D2      addq  %rdx, %rdx             #Add rdx to itself
55 008b 4801D0      addq  %rdx, %rax             #Add offset to the address
56 008e 8B4485C0    movl  -64(%rbp,%rax,4), %eax #Load *(rbp - 4 + (rax * 4)) into eax (get arr[y][x])

12:test.c        ****     printf("%d\n",arrflat[i]);
80                  .loc 1 12 0 discriminator 3
81 00c0 8B45BC      movl  -68(%rbp), %eax        #Load address -62 bytes from the memory pointed to by %rbp
82 00c3 4898        cltq                         #Convert this into a 64-bit integer address (where is `arrflat`?)
83 00c5 8B4485E0    movl  -32(%rbp,%rax,4), %eax #Load *(rbp - 4 + (rax * 4)) into eax (get arrflat[i])
Run Code Online (Sandbox Code Playgroud)