基本原则
对于数据类型T和整型常数N,声明如下即为数组:
T a[N];
起始位置表示为Xa,这个声明有两个效果:
- 在内存中分配一个
sizeof(T)*N
字节的连续的区域 - 引入标识符a,可以用a来作为指向数组开头的指针,这个指针的值就是Xa
而在x86-64中的内存引用指令可以用来简化数组访问,比如说,E是一个int
型的数组,而我们想要计算E[i],假设E的地址放在寄存器%rdx
中,i 放在寄存器%rcx
中,那么访问E[i]的汇编指令便是这个:
movl (%rdx,%rcx,4),%eax
这个指令会执行计算Xe+4i
,之后读出这个内存的值,将结果放到寄存器%eax
中。
指针运算
可以说C/C++ 的灵魂就是就是指针了,指针计算出来的值会根据该指针引用的数据类型的大小进行伸缩。也就是说,如果 p 是一个指向类型为T的数据的指针,p的值为Xp,那么表达式 p+i 的值为Xp+sizeof(T)+i
。
单个操作数操作符&
和*
可以产生指针和间接引用指针。下表是刚刚例子的扩展:
练习题
练习题答案
嵌套数组
当我们创建嵌套数组的时候,数组分配和引用的规则其实也是成立的。比如下面的声明:
int A[5][3];
等价于下面的声明:
typedef int row3_t[3];
row3_t A[5];
要访问这样的多维数组的元素,编译器会以数组起始为基地址,偏移量为索引,产生计算期望的元素的偏移量,然后使用某种mov指令。
通常来说,对于一个如下声明的数组:
T D[R][C];
它的数组元素D[i][j]的内存地址为:
&D[i][j] = Xd + sizeof(T)*(C*i + j)
那么假设Xd、i、j 分别在寄存器%rdi
、%rsi
、%rdx
中,那么可以看一下下面的汇编:
//A in %rdi,i in %rsi,j in %rdx
leaq (%rsi, %rsi,2), %rax //3i
leaq (%rdi , %rax,4), %rax //Xd+12i
movl (%rax , %rdx,4), %eax //M[Xd+12i+4j]
练习题
练习题答案
参考文献
[1] 深入理解计算机系统 第三章 程序的机器级表示