Передача параметров в функцию

Последнее обновление: 19.07.2023

Функции могут принимать параметры и возвращать некоторые значения. Обычно для передачи параметров применяются регистры. Например, первый параметр помещается в регистр X0, второй параметр - в X1 и т.д. Поскольку количество регистров ограничено, и они могут использоваться для других целей, также можно передавать значения параметров через стек. Можно комбинировать различные подходы.

Возвращаемое значение обычно помещается в один из регистров - обычно в X0. Если данные не вмещаются в один регистр, например, 128-битное число, то можно использовать несколько регистов, например, X0 и X1.

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

Рассмотрим простейший пример - функцию, которая складывает два числа:

.global _start 
_start: 
    MOV X0, #5          // первый параметр
    MOV X1, #6          // второй параметр
    BL sum              // вызываем функцию sum

    MOV X8, #93       // устанавливаем функцию Linux для выхода из программы
    SVC 0             // Вызываем функцию Linux

// Функция sum сладывает два числа
// Параметры:
// X0 - первое число
// X1 - второе число
sum:
    ADD X0, X0, X1
    RET

Функция sum использует инструкцию ADD для сложения двух чисел, которые располагаются в регистрах Х0 и Х1.

В основной программе перед вызовом функции sum устанавливаются значения для этих параметров:

MOV X0, #5          // первый параметр
MOV X1, #6          // второй параметр
BL sum              // вызываем функцию sum

Стоит отметить, что есть различные соглашения по поводу того, как следует передавать параметры. Наиболее распространенный в данном случае стандарт - стандарт AAPCS (Procedure Call Standard for the Arm Architecture) устанавливает, что для передачи целочисленных параметров в функцию используются регистры X0-X7. Указатели (адреса) представляют 8-байтное целочисленное значение, поэтому также передаются через эти регистры. Для передачи чисел с плавающей точкой используются регистры V0-V7. Данные, размер которых превышает размер регистра, разбиваются на несколько регистров (при их доступности) или передаются через стек.

Копирование строки

Рассмотрим более сложный, но более практичный пример - копирование одной строки в другую:

// METANIT.COM. Определение функции с параметрами
.global _start 
_start: 
    LDR X0, =input     // строка, их которой надо скопировать символы
    LDR X1, =output    // строка, в которую надо скопировать символы
    BL copy          // вызов функции copy
    MOV X0, 0         // код возврата - 0
    MOV X8, #93       // устанавливаем функцию Linux для выхода из программы
    SVC 0             // Вызываем функцию Linux

// определение функции copy, которая копирует символы из одной строки в другую
// X0 - адрес входящей строки
// X1 - адрес исходящей строки
copy:
    MOV X4, X1          // сохраняем адрес начала строки, чтобы потом вычислить ее длину
// в цикле получаем все байты, пока не дойдем до нулевого байта
loop: 
    LDRB W5, [X0], #1        // загружаем из X0 один байт - один символ в W5 и увеличиваем адрес в X0 на 1 байт
    CMP W5, #0              // сравниваем с нулевым байтом
    B.EQ endloop            // если нулевой байт, переход к метке endloop 
    STRB W5, [X1], #1      // если символы равны, заменяем байт по адресу X1 и увеличиваем адрес в X1 на 1 байт
    B loop                  // перед обратно к метке loop
// печать строки на консоль
endloop:
    SUB X2, X1, X4      // длина строки X2 = X1 - X4
    MOV X1, X4          // в X4 сохранен начальный адрес генерируемой строки
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux
    RET                 // выход из функции

.data
    input: .asciz "Hello METANIT.COM!\n"
    output:  .fill 20, 1, 0

Здесь определена функция copy, которая принимает два параметра - строку, из которой надо скопировать символы, и строку, в которую надо скопировать символы. Адрес исходной строки передается через регистр X0, а адрес генерируемой строки передается через регистр X1.

Здесь мы предполагаем, что строка завершается нулевым байтом. Для этого определяем исходную строку с помощью директивы .asciz. Строку, в которую надо скопировать определяется фактически как набор из 20 байт, которые заполнены нулями. Кроме того, здесь подразумевается, что каждый символ строки равен 1 байту (то есть кириллица не пройдет). Стоит отметить, что мы в данном случае не проверяем соответствие строк по длине и предполагаем, что вторая строка достаточно большая, чтобы вместить все символы из первой строки.

В самой функции, поскольку мы сами будет вычислять длину строки, сохраняем начальный адрес строки из X1 в регистр X4:

MOV X4, X1

Далее в цикле загружаем каждый байт по адресу X1 в регистр W5:

LDRB W5, [X0], #1

При этом адрес в X0 увеличивается на 1 байт, чтобы в следующий раз мы взяли следующий символ.

Затем сравниваем символ с нулевым байтом, и если равенство верно, завершаем цикл и переходим к метке endloop

CMP W5, #0        // сравниваем с нулевым байтом
B.EQ endloop

Если байт не нулевой, копируем во вторую строку по адресу, который хранится в X1

STRB W5, [X1], #1      // если символы равны, заменяем байт по адресу X1 и увеличиваем адрес в X1 на 1 байт
B loop    

При этом увеличиваем значение адреса на 1, чтобы в следующий раз положить байт по следующему адресу. И переходим обратно к метке loop.

Поскольку в X4 сохранен начальный адрес, а в X1 после добавления символов и приращений адреса хранится конечный адрес, то мы можем получить длину строки вычитанием:

SUB X2, X1, X4      // длина строки X2 = X1 - X4

И в конце функции выводим полученную строку на консоль.

В общей части программы загружаем адреса строк в регистры и вызываем функцию copy

LDR X0, =input     // строка, их которой надо скопировать символы
LDR X1, =output    // строка, в которую надо скопировать символы
BL copy          // вызов функции copy

Функция замены символа в строке

Другой пример - замена в строке одного символа на другой:

.global _start 
_start: 
    LDR X0, =input     // строка для вывода на экран
    MOV X1, #'l'        // символ, который надо заменить
    MOV X2, #'*'         // символ, на который надо заменить
    BL replace          // вызов функции replace

    MOV X0, 0         // код возврата - 0
    MOV X8, #93       // устанавливаем функцию Linux для выхода из программы
    SVC 0             // Вызываем функцию Linux

// определение функции replace, которая заменяет один символ на другой
// X0 - адрес строки
// W1 - символ, который надо заменить
// W2 - символ, на который надо заменить
replace:
    MOV X4, X0          // сохраняем адрес начала строки, чтобы потом вычислить ее длину
// в цикле получаем все байты, пока не дойдем до нулевого байта
loop: 
    LDRB W5, [X0], #1           // загружаем из X0 один байт - один символ в W5
    CMP W5, W1                  // сраниваем с символом, который надо заменить
    B.NE ifzero                 // если символы НЕ равны, то переходим для проверки нулевого байта на метку ifzero
    STRB W2, [X0, #-1]          // если символы равны, заменяем байт по адресу X0-1
ifzero:
    CMP X5, #0              // сравниваем с нулевым байтом
    B.NE loop               // если НЕнулевой байт, переход обратно к метке loop 
    
// печать строки на консоль
    MOV X1, X4          // в X4 сохранен начальный адрес строки
    SUB X2, X0, X4      // длина строки X2 = X0 - X4
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux
    RET                 // выход из функции

.data
    input: .asciz "Hello World!\n"

Здесь функция replace принимает три параметра:

  • В регистр X0 помещается адрес строки, где надо выполнить замену.

  • В регистр W1 - символ, который надо заменить.

  • В регистр W2 - символ, на который надо заменить.

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

CMP W5, W1
STRB W2, [X0, #-1]          // если символы равны, заменяем байт по адресу X0-1

Если символы совпадают, то заменяем данный символ в строке на символ из регистра W2. Поскольку при считывании символа мы увеличили значение адреса в регистре X0 на 1 байт, то при замене символа вычитаем один байт.

При вызове функции устанавливаем три вышеуказанных параметра:

LDR X0, =input     // строка для вывода на экран
MOV X1, #'l'         // символ, который надо заменить
MOV X2, #'*'         // символ, на который надо заменить
BL replace          // вызов функции replace

Таким образом, в строке "Hello World!\n" заменяем символ "l" на символ "*", поэтому в итоге получим строку "He**o Wor*d!\n"

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850