“基于指针的数组访问”还有一个额外的可能含义或含义:
您可能有一个指向数组的指针,而不是位于固定地址的数组。实际上,在 C/C++ 中,“指向数组的指针”实际上通常只是指向数组第一个元素的指针。基本上,您有一个作为函数参数的数组,或者一个指向作为结构或类成员的数组的指针:
void Foo(char a[]);
/*or*/ void Foo(char *a);
struct Bar { int offset4bytes; char* a; };
通常,当您要使用这样的数组时,数组的基地址将被加载到寄存器中。
现在假设您要访问这样一个数组的元素 i,
char tmp = a[i];
假设 r1 包含数组地址。 (实际上,可能是由调用约定指定的不同寄存器用于函数参数。编译器发现可用于其他代码的任何内容。)
假设 i 位于寄存器 r2 中。
并且,为了更好地衡量,让 tmp 为 r3。
在某些指令集上,例如Intel x86,有一种寻址模式,看起来像
MOV r3, (r2,r1)4
即寻址模式可以添加两个寄存器和一个偏移量(我在结构示例中任意添加了一个字段,以便我可以展示这一点)。
哎呀,他们甚至可以缩放其中一个寄存器,即所谓的索引寄存器:
MOV r3, (r2*2,r1)4
或者我更喜欢这样写
r3 := load( Memory[r2<<1+r1+4]
然而,MIPS 没有这种基址+_index*scale+offset 寻址模式。大多数MIPS内存访问指令仅限于寄存器+偏移量。因此,对于 MIPS,您可能必须这样做
ADDU r10, r1,r1 ; dest on left. r10 = 2*r1
ADDU r11, r2,r10
LB r3,(r11)4
也就是说,您可能需要添加额外的 RISC 指令来完成 x86 在具有复杂寻址模式的一条 CISC 指令中所做的事情。然而,这种寻址并不常见,并且通常可以避免。
此外,即使只是在固定地址寻址数组也可能需要 MIPS 中的额外指令。 x86 指令可以有 32 位内存偏移量 - 在这种情况下实际上可能是数组的绝对地址。 MIPS 指令仅限于 16 位偏移量 - MIPS 指令是固定宽度的,即 32 位宽。因此,甚至可能需要单独的指令来访问固定地址处的数组,通常是将地址的高位加载到寄存器中。
还有更多 - MIPS 有更新的指令,如 LUXC1,具有 reg+reg 寻址模式。但没有缩放索引,也没有第三个偏移分量。
由于这种受限的寻址模式,天真的编译器为循环生成的代码如下
for(int i=0;i<N;i++) {
this->a[i] = 0;
}
如果循环包含上述多指令序列,效率会很低。
一个循环,例如
for(char *p=this->a;p<&(this->a[N]);p++) {
*p=0;
}
或者,等价地
for(char *p=this->a;p<this->a+N;p++) {
*p;
}
甚至有时
for(i=-N,*p=this->a;i<0;i++,p++) {
*p=0;
}
可能会更有效,因为,例如在前两个中,只有一条指令来执行存储。 (如果遍历多个数组,最后一个通常才是胜利。
现在,在一个简单的示例中,任何好的编译器都会为您执行此优化。但有时编译器更喜欢这种基于指针的访问。