考虑您典型的 gcc 编译器(C99 模式)并考虑一个数组:
char array[2][4];
Run Code Online (Sandbox Code Playgroud)
显然编译器会编译(我假设它在翻译过程中?)使目标机器(假设它是 X86)以它“解决”的方式工作的代码,或者我们可以说,“分配”两个地址array[0][0] 和 array[1][0] 在访问其中任何一个之前(我可能完全错了)。我的问题是编译器如何“知道”这一点,因为它只是一个愚蠢的程序?它是某种简单的递归算法,做得非常正确,所以我们真的不必关心有多少维(如“哦,名称“数组”后面有一个括号对?我只是翻译它进入一个地址,等待,有2个?地址然后)或那些设计编译器的人专门研究了这种情况并编写了编译器来解决它?
如果您对我的问题感到困惑,请考虑一维数组 arr[2]。我可以让 arr 参与各种计算,知道它只是一个地址,可以说是“起始地址”。但是对于一维数组,您只需要一个“起始地址”,这在编译期间很容易完成,因为编译器只会将该名称(在本例中为 arr)转换为未初始化的地址(同样,我可能完全错了),但是对于一个二维数组,编译器需要处理多个地址,它是如何工作的?
汇编代码会是什么样子?
一个二维数组,例如
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)
并且您可以仅使用它们的地址(分别为arr和arrflat)来访问和操作这两个数组。
但是,当您通过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)