Практика. Программа перевода строки в верхний регистр

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

Напишем программу по переводу строку в верхний регистр. Псевдокод такой программы мог бы выглядеть следующим образом:

i = 0
DO
    char = inStr[i]
    IF char >= 'a' AND char <= 'z' THEN
        char = char - ('a' - 'A')
    END IF
    outStr[i] = char
 i = i + 1
WHILE char == 0
PRINT outStr

Здесь предполагается, что строка завершается нулевым байтом (как например, в языке Си), то есть в данном случае строка представляет набор символов, которые заканчиваются нулевым символом 0 или \0. Соответственнои в цикле DO..WHILE считываем каждый символ из строки inStr, пока он не равен 0.

В цикле, если символ является строчным, то вычитаем из его числового кода ascii расстояние до его аналога в верхнем регистре (оно равно разнице 'a' - 'A'). Полученный символ передаем в генерируемую выходную строку outStr.

Реализация на ассемблере:

// Перевод строки в верхний регистр
//
// X0-X2 - параметры системных функций Linux
// X3 - адрес генерируемой строки
// X4 - адрес исходной строки
// W5 - текущий обрабатываемый символ
// X8 - номер вызываемой функции функций Linux

.global _start 
_start: 
    LDR X4, =instr      // загружаем адрес входной строки
    LDR X3, =outstr     // загружаем адрес строки, которая будет генерироваться
// в цикле считываем по байту, пока адрес в регистре X1 не будет указывать на 0 
loop: 
    LDRB W5, [X4], #1   // загружаем в W5 символ и увеличиваем адрес в X4 на 1
    CMP W5, #'z'        // если символ W5 > 'z', переходим к метке endif
    B.GT endif
    
    CMP W5, #'a'        // иначе если W5 < 'a', то также переходим к метке endif
    B.LT endif           
    
    SUB W5, W5, #('a'-'A')  // в остальных случаях конвертируем букву в верхний регистр
endif:  
    STRB W5, [X3], #1       // сохраняем символ в строку outstr и увеличиваем адрес в X3 на 1
    CMP W5, #0              // завершаем обработку, если символ представляет 0
    B.NE loop               // но если символ не равен 0, переходим обратно к метке loop

// Вывод строки на консоль
    MOV X0, #1              // Номер потока для вывода - 1 = StdOut
    LDR X1, =outstr         // выводимая строка
    SUB X2, X3, X1          // получаем длину строки с помощью X3-X1
    MOV X8, #64             // устанавливаем функцию Linux для вывода в поток
    SVC 0                   // Вызываем функцию Linux
// выход из программы
    MOV X0, #0              // 0 - код возврата из программы
    MOV X8, #93             // устанавливаем функцию Linux для выхода из программы
    SVC 0                   // Вызываем функцию Linux
.data
    instr: .asciz "Hello Metanit.com\n"
    outstr: .fill 19, 1, 0

Вначале загружаем адреса обоих строк

LDR X4, =instr
LDR X3, =outstr

Выходная строка здесь определена как набор из 19 байт. А исходная строка определяется с помощью директивы .asciz, поэтому к ней автоматически добавляется нулевой байт.

С помощью инструкции LDRB считываем один байт из исходной строки и увеличиваем адрес, чтобы в следующий раз взять следующий символ.

LDRB W5, [X4], #1

Далее нам надо узнать, что за символ и нужно ли его переводить в верхний регистр (так как он уже может быть в верхнем регистре или это не буква):

 CMP W5, #'z'
B.GT endif

CMP W5, #'a'
B.LT endif  

Если символ вне диапазона a-z, то переходим к метке endif. Если же попадает в диапазон, выполняем вычитание для получения кода символа в верхнем регистре:

SUB W5, W5, #('a'-'A')

Далее после метки endif инструкция STRB помещает байт из W5 (числовой код символа) в строку, на которую указывает адрес в регистре X3. При этом к адресу из X3 добавляется 1, чтобы потом установить следующий символ:

STRB W5, [X3], #1
CMP W5, #0
B.NE loop

Кроме того, сравниваем текущий символ с 0, чтобы узнать об окончании строки. Если символ не 0, переходим обратно к метке loop.

Далее устaнавливаются параметры для выводы с помощью функции Linux на консоль. Обратите внимание, как вычисляется длина строки:

SUB X2, X3, X1

X3 в конце программы хранит адрес последнего символа исходной строки. Соответственно если мы вычтем из него адрес начала строки - X1, то получим длину в байтах (каждый символ занимает один байт)

Исходная строка в данном случае "Hello Metanit.com\n", соответственно на консоли мы увидим "HELLO METANIT.COM"

Оптимизация программы

Стоит отметить, что мы можем оптимизировать программу в следующем месте:

CMP W5, #'z'        // если символ W5 > 'z', переходим к метке endif
B.GT endif
    
CMP W5, #'a'        // иначе если W5 < 'a', то также переходим к метке endif
B.LT endif           

Но по сути если мы отнимем от кода текущего символа строки числовой код символа 'a', то мы получим расстояние между этими двумя символами. Если текущий символ строки представляет символ латинского алфавита в нижнем регистре, то разность будет в диапазоне от 0 до 25 (ведь в латинсокм алфавите 25 символов).

Разность меньше нуля означает, что текущий символ строки уже в верхнем регистре, либо представляет число, либо другой символ ASCII, который идет до символа 'a'. Однако если мы будем рассматривать разность как число без знака (которое не может быть отрицательным), то отрицательные значения в данном случае мы можем отбросить. И будет достаточно сравнивать, больше ли разность, чем число 25. Так, изменим программу следующим образом:

// Перевод строки в верхний регистр
//
// X0-X2 - параметры системных функций Linux
// X3 - адрес генерируемой строки
// X4 - адрес исходной строки
// W5 - текущий обрабатываемый символ
// W6 - разность кода текущего символа строки и кода символа 'a'
// X8 - номер вызываемой функции функций Linux
 
.global _start 
_start: 
    LDR X4, =instr      // загружаем адрес входной строки
    LDR X3, =outstr     // загружаем адрес строки, которая будет генерироваться
// в цикле считываем по байту, пока адрес в регистре X1 не будет указывать на 0 
loop: 
    LDRB W5, [X4], #1   // загружаем в W5 символ и увеличиваем адрес в X4 на 1
    SUB W6, W5, #'a'    // Узнаем, является ли символ  символом в нижнем регистре
    CMP W6, #25         // сравниваем разность с 25 - для символов в нижнем регистре разность должна быть 0-25
    B.HI endif          // если больше 25      
     
    SUB W5, W5, #('a'-'A')  // в остальных случаях конвертируем букву в верхний регистр
endif:  
    STRB W5, [X3], #1       // сохраняем символ в строку outstr и увеличиваем адрес в X3 на 1
    CMP W5, #0              // завершаем обработку, если символ представляет 0
    B.NE loop               // но если символ не равен 0, переходим обратно к метке loop
 
// Вывод строки на консоль
    MOV X0, #1              // Номер потока для вывода - 1 = StdOut
    LDR X1, =outstr         // выводимая строка
    SUB X2, X3, X1          // получаем длину строки с помощью X3-X1
    MOV X8, #64             // устанавливаем функцию Linux для вывода в поток
    SVC 0                   // Вызываем функцию Linux
// выход из программы
    MOV X0, #0              // 0 - код возврата из программы
    MOV X8, #93             // устанавливаем функцию Linux для выхода из программы
    SVC 0                   // Вызываем функцию Linux
.data
    instr: .asciz "Hello Metanit.com\n"
    outstr: .fill 19, 1, 0

Использование инструкции CSEL

Мы можем пойти дальше и вообще убрать один из переходов и сделать программу более простой:

.global _start 
_start: 
    LDR X4, =instr      // загружаем адрес входной строки
    LDR X3, =outstr     // загружаем адрес строки, которая будет генерироваться
// в цикле считываем по байту, пока адрес в регистре X1 не будет указывать на 0 
loop: 
    LDRB W5, [X4], #1   // загружаем в W5 символ и увеличиваем адрес в X4 на 1
    SUB W6, W5, #'a'    // Узнаем, является ли символ  символом в нижнем регистре
    CMP W6, #25         // сравниваем разность с 25 - для символов в нижнем регистре разность должна быть 0-25
      
    SUB W6, W5, #('a'-'A')  // конвертируем букву в верхний регистр
    CSEL W5, W6, W5, LS   // если разность символов меньше 25, W5 = W6
    STRB W5, [X3], #1       // сохраняем символ в строку outstr и увеличиваем адрес в X3 на 1
    CMP W5, #0              // завершаем обработку, если символ представляет 0
    B.NE loop               // но если символ не равен 0, переходим обратно к метке loop
  
// Вывод строки на консоль
    MOV X0, #1              // Номер потока для вывода - 1 = StdOut
    LDR X1, =outstr         // выводимая строка
    SUB X2, X3, X1          // получаем длину строки с помощью X3-X1
    MOV X8, #64             // устанавливаем функцию Linux для вывода в поток
    SVC 0                   // Вызываем функцию Linux
// выход из программы
    MOV X0, #0              // 0 - код возврата из программы
    MOV X8, #93             // устанавливаем функцию Linux для выхода из программы
    SVC 0                   // Вызываем функцию Linux
.data
    instr: .asciz "Hello Metanit.com\n"
    outstr: .fill 19, 1, 0

Ключевой момент здесь

CSEL W5, W6, W5, LS

Эта инструкция помещает в W5 символ из W6, если верно условие LS. Иначе в W5 остается то значение, которое было там ранее.

Условие LS устанавливается предыдущей инструкцией CMP - если она выявила, что в регистре W6 число меньше или равно 25 (то есть символ в нижнем регистре), то условие LS будет верно.

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