Материал: Архитектура и программирование MIPS-процессоров. Разинкин К.А

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

2.5. Массивы

Массивы удобно использовать для доступа к большому количеству однородных или сходных данных. Массив располагается в ячейках памяти со строго последовательными адресами и занимает непрерывный участок памяти. Каждый массив состоит из последовательности элементов одинакового размера, и каждый элемент массива имеет порядковый номер, называемый индексом. В этом разделе показано, как получать доступ к элементам массива в памяти.

Индексация массивов

На рис. 2.12 показан массив из пяти элементов – целых чисел. Элементы имеют индексы от 0 до 4. Массив хранится в оперативной памяти, начиная с базового адреса 0x10007000. Базовый адрес определяет адрес самого первого элемента массива, array[0].

В следующем примере кода первые два элемента массива array умножаются на 8 и помещаются обратно в массив.

Пример кода Доступ к массиву Код на языке высокого уровня

int array[5];

array[0] = array[0] * 8; array[1] = array[1] * 8;

Код на языке ассемблера MIPS

# $s0 = base address of array

lui $s0, 0x1000

# $s0 = 0x10000000

ori $s0, $s0, 0x7000

# $s0 = 0x10007000

lw

$t1, 0($s0)

# $t1 = array[0]

sll $t1, $t1, 3

# $t1 = $t1 << 3 = $t1 * 8

sw

$t1, 0($s0)

# array[0] = $t1

lw

$t1, 4($s0)

# $t1 = array[1]

sll $t1, $t1, 3

# $t1 = $t1 << 3 = $t1 * 8

sw

$t1, 4($s0)

# array[1] = $t1

 

 

71

Рис. 2.12. Массив из пяти элементов

Первым шагом при доступе к элементам массива является загрузка базового адреса массива в регистр. В примере кода 6.21 базовый адрес загружается в $s0. Вспомним, что инструкции загрузки константы в старшие 16 бит (lui) и побитовое логическое «ИЛИ» с константой (ori) можно использовать для загрузки 32-битной константы в регистр.

Из предыдущего примера кода также становится понятно, почему инструкция lw вычисляет эффективный адрес путем сложения базового адреса и смещения. Базовый адрес указывает на начало массива, а смещение можно использовать для доступа к последующим его элементам. Так, например, элемент array [1] хранится в памяти по адресу 0x10007004, т.е. на одно слово дальше, чем array [0], поэтому доступ к нему осуществляется со смещением 4 от базового адреса.

Вы могли заметить, что для манипуляций с двумя элементами массива в предыдущем примере кода по существу используется один и тот же код, разница состоит лишь в значениях индексов этих элементов. Дублирование кода не представляет проблемы, когда мы работаем с двумя элементами массива, но такой подход становится крайне неэффективным, когда необходим доступ ко многим элементам массива.

В следующем примере кода использован цикл for для умножения на 8 всех элементов массива, состоящего из 1000 элементов и находящегося в памяти по базовому адресу

0x23B8F000.

72

Пример. Доступ к массиву с помощью цикла for Код на языке высокого уровня

int i;

int array[1000];

for (i = 0; i < 1000; i = i + 1) array[i] = array[i] * 8;

Код на языке ассемблера MIPS

#$s0 = array base address, $s1 = i

#initialization code

lui $s0, 0x23B8

 

# $s0 = 0x23B80000

ori $s0, $s0, 0xF000 # $s0 = 0x23B8F000

addi $s1, $0, 0

 

# i = 0

addi $t2, $0, 1000

# $t2 = 1000

loop:

 

 

 

slt

$t0, $s1, $t2

 

# i < 1000?

beq

$t0, $0, done

# if not, then done

sll

$t0, $s1, 2

 

# $t0 = i*4 (byte offset)

add

$t0, $t0, $s0

 

# address of array[i]

lw

$t1, 0($t0)

 

# $t1 = array[i]

sll

$t1, $t1, 3

 

# $t1 = array[i] * 8

sw

$t1, 0($t0)

 

# array[i] = array[i] * 8

addi $s1, $s1, 1

 

# i = i + 1

j

loop

# repeat

done:

 

 

 

На рис. 2.13 показан массив, состоящий из 1000 элементов. В качестве индекса массива теперь используются не константы, а переменная i, поэтому мы не можем использовать непосредственные операнды в качестве смещения для инструкции lw. Вместо этого мы вычисляем адрес i-го элемента. Вспомним, что каждый элемент нашего массива – это четырёхбайтовое слово. Поскольку память адресуется побайтно, то смещение i-го элемента относительно базового адреса массива будет равно i*4. Сумма этого смещения и базового адреса массива и даст нам адрес i-го элемента в памяти. Для вычисления

73

i*4 можно воспользоваться инструкцией сдвига влево на 2 бита.

Рис. 2.13. Размещение array[1000] в памяти

2.6. Вызовы функций и работа со стеком

В языках высокого уровня обычно используют функции, или процедуры, для повторного использования часто выполняемого кода и для того, чтобы сделать программу модульной и читаемой. У функций есть входные параметры, называемые аргументами, и выходной результат, называемый возвращаемым значением. Функции должны вычислять возвращаемое значение, не вызывая неожиданных побочных эффектов. Когда одна функция вызывает другую, вызывающая функция и вызываемая функция должны прийти к соглашению о том, где размещать аргументы и возвращаемое значение. Следуя соглашениям, принятым в архитектуре MIPS, вызывающая функция размещает до четырёх аргументов в регистры $a0–$a3 перед тем, как произвести вызов, а вызываемая функция помещает возвращаемое значение в регистры $v0–$v1 перед тем, как завершить работу. Следуя этому соглашению, обе функции знают, где искать аргументы и куда возвращать значение, даже если вызывающая и вызываемая функции были написаны разными людьми. Вызываемая функция не должна вмешиваться в работу

74

вызывающей функции. Вкратце, это означает, что вызываемая функция должна знать, куда передать управление после завершения работы, и она не должна портить значения любых регистров или памяти, которые нужны вызывающей функции. Вызывающая функция сохраняет адрес возврата (англ.: return address) в регистре адреса возврата ($ra) в тот момент, когда она передаёт управление вызываемой функции путем выполнения инструкции безусловного перехода с возвратом (jal).

Вызываемая функция не должна изменять архитектурное состояние и содержимое памяти, от которых зависит вызывающая функция.

В частности, вызываемая функция должна оставить неизменным содержимое сохраняемых регистров $s0–$s7, регистра $ra и стека – участка памяти, используемого для хранения временных переменных.

Вызовы и возвраты из функций

Архитектура MIPS использует инструкцию безусловного перехода с возвратом (jal) для вызова функции и инструкцию безусловного перехода по регистру (jr) для возврата из функции. Представленный ниже пример кода демонстрирует главную функцию main, вызывающую функцию simple. Здесь функция main является вызывающей, а simple – вызываемой.

Функция simple не получает входных аргументов и ничего не возвращает, она просто передаёт управление обратно вызывающей функции. В примере кода слева от каждой инструкции MIPS приведены их адреса в шестнадцатеричном формате.

Код на языке высокого уровня int main() {

simple();

…

}

// void means the function returns no value

75

Источник: https://studfile.net/preview/16568788/