Что мы подразумеваем под доступом к массиву на основе указателя в MIPS?
Доступ к массиву на основе указателя в MIPS
Ответы (2)
Существует дополнительное возможное значение или значение «доступа к массиву на основе указателя»:
У вас может быть указатель на массив, а не на массив с фиксированным адресом. На самом деле в C/C++ "указатель на массив" обычно является просто указателем на первый элемент массива. По сути, у вас есть массив, являющийся параметром функции, или указатель на массив, являющийся членом структуры или класса:
void Foo(char a[]);
/*or*/ void Foo(char *a);
struct Bar { int offset4bytes; char* a; };
Обычно, когда вы хотите использовать такой массив, базовый адрес массива загружается в регистр.
Теперь скажем, что вы хотите получить доступ к элементу i такого массива,
char tmp = a[i];
Предположим, что r1 содержит адрес массива. (На самом деле, вероятно, это другой регистр, указанный в соглашении о вызовах, для параметров функции. Все, что компилятор найдет доступным для другого кода.)
Допустим, я живёт в регистре r2.
И, на всякий случай, пусть tmp будет r3.
В некоторых наборах инструкций, например. Intel x86, есть режим адресации, который выглядит как
MOV r3, (r2,r1)4
т. е. режим адресации может добавить два регистра и смещение (я произвольно добавил поле в пример структуры, чтобы показать это).
Черт возьми, они даже могут масштабировать один из регистров, так называемый индексный регистр:
MOV r3, (r2*2,r1)4
или как я предпочитаю писать
r3 := load( Memory[r2<<1+r1+4]
Однако MIPS не имеет такого режима адресации base+_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;
}
может быть более эффективным, потому что, например. в первых двух будет только одна инструкция для сохранения. (Последнее обычно выигрывает только при обходе нескольких массивов.
Теперь, в простом примере, любой хороший компилятор сделает эту оптимизацию за вас. Но иногда компилятор предпочитает такой доступ на основе указателя.
У вас будет указатель, указывающий на начало массива значений. Для обхода массива вы будете использовать арифметику указателя (обычно добавление/вычитание 1, 2 или 4), чтобы переместить указатель на следующий/предыдущий элемент в массиве.