Строки

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

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

Строки представляют набор значений, который хранится в непрерывной области памяти. Процессор x86-64 поддерживает работу с 4 типами строк: строки значений byte, строки из чисел word, строки из двойных слов и строки из четверных слов. Для работы с этим строками процессор x86-64 предоставляет ряд инструкций для копирования, сравнения, поиска в строке и ряд других инструкций, которые далее будут рассмотрены.

Строковые инструкции используют определенные регистры в качестве операндов:

  • RSI: хранит индекс значений исходной строки

  • RDI: хранит индекс значений целевой строки

  • RCX: счетчик значений

  • AL, AX, EAX и RAX

  • Флаги в регистре FLAGS

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

Для копирования строк применяются следующие инструкции:

  • movsb: извлекает байт по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает регистры RSI и RDI на 1.

  • movsw: выбирает слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает RSI и RDI на 2.

  • movsd: извлекает двойное слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает адрес в регистрах RSI и RDI на 4.

  • movsq: извлекает четверное слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает адрес в регистрах RSI и RDI на 8.

Например, скопируем одну строку в другую:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17
nums2: .fill 8, 2, 0  # 8 числа по 2 байта равных 0
.text
_start:
    movq $nums1, %rsi   # в RSI - откуда копируем
    movq $nums2, %rdi   # в RDI - куда копируем
    movq $8, %rcx       # в RCX - сколько копируем
    movsw               # выполняем копирование
    # проверяем
    movq nums2, %rdi    # RDI=10

    movq $60, %rax
    syscall

Здесь копируем данные из nums1 в nums2. Для этого адрес исходной строки nums1 загружаем в RSI, а адрес целевой строки nums2 - в регистр RDI. В RCX помещаем количество элементов , которые надо скопировать. Важно, что речь идет о количестве элементов, а не байт. Так, в данном случае мы хотим скопировать все 8 элементов из nums1 в nums2, которые в целом занимают 16 байт. И для копирования слов выполняем инструкцию movsw.

Однако по умолчанию все инструкции копирования помещают только первый элемент исходной строки в целевую строку. Но нам то нужно в данном случае скопировать 8 чисел. Для повторения копирования применяется префикс rep

Префикс rep

Префикс rep (repeat повторять) указывается перед инструкцией, которая работает со строками. Так, если префикс rep присутствует, при выполнении инструкции movsb процессор проверяет RCX, чтобы увидеть, содержит ли этот регистр число 0. Если не содержит, то процессор перемещает байт из RSI в RDI и уменьшает значение в регистре RCX. Этот процесс повторяется до тех пор, пока RCX не станет равным 0. Если RCX содержит 0 при первоначальном выполнении, инструкция movsb не будет копировать байты данных.

Например, изменим предыдущую программу, применив rep:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17
nums2: .fill 8, 2, 0  # 8 числа по 2 байта равных 0
.text
_start:
    movq $nums1, %rsi
    movq $nums2, %rdi
    movq $8, %rcx
    rep movsw

    movq $60, %rax
    syscall

Префикс rep указывает процессору повторить эту операцию столько раз, сколько указано в регистре RCX.

Стоит отметить, что конкретно в случае выше процесс копирования не является оптимальным: нам надо скопировать 8 слов или 16 байт, и это количество кратно 8. На первый взгляд для копирования слов надо использовать инструкцию movsw, которая собственно и предназначена для копирования слов. Но для ассемблера в данном случае нет разницы - скопирует он 8 раз по 2 байта последовательно расположенные данные или скопирует те же данный 2 раза но по 8 байт. Поэтому в случае выше мы могли бы применить копирование по восьмеричным словам:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17
nums2: .fill 8, 2, 0  # 8 числа по 2 байта равных 0
.text
_start:
    movq $nums1, %rsi
    movq $nums2, %rdi
    movq $2, %rcx       # 8 слов - это два четверных слова quad
    rep movsq           # копируем по четверному слову quad

    movq $nums2, %rdi    
    movq 4(%rdi), %rdi  # проверяем 4-й элемент из nums2, он должен быть равен 12

    movq $60, %rax
    syscall

Если количество байт не кратно 8 или вообще нечетное, то для большей эффективности можно комбинровать копирование различных данных:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17, 18
nums2: .fill 9, 2, 0
.text
_start:
    movq $nums1, %rsi
    movq $nums2, %rdi
    movq $2, %rcx       # 9 слов - это 2 четверных слова + 1 слово

    rep movsq       # копируем по четверному слову quad
        movsw       # копируем одно слово 

    movq $nums2, %rdi    
    movq 16(%rdi), %rdi  # проверяем 8-й элемент из nums2, он должен быть равен 18

    movq $60, %rax
    syscall

В данном случае для копирования данных потребуется две инструкции movsq и одна инструкция movsw. Даная схема наиболее эффективна, если оба массива выровнены по границам четырех слов. Если подобного выравнивания нет, то можно переместить инструкции, которые копируют меньшее количество данных - в данном случае movsw до инструкции movsq, чтобы movsq работала с данными, выровненными по четырём словам:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17, 18
nums2: .fill 9, 2, 0 
.text
_start:
    movq $nums1, %rsi
    movq $nums2, %rdi
    movq $2, %rcx       # 9 слов - это 2 четверных слова + 1 слово

    movsw       # копируем одно слово 
    rep movsq       # копируем по четверному слову quad

    movq $nums2, %rdi    
    movq 16(%rdi), %rdi  # проверяем 8-й элемент из nums2, он должен быть равен 18

    movq $60, %rax
    syscall

Направление копирования

Флаг направления в регистре FLAGS управляет навправлением копирования элементво строк. Если флаг направления сброшен, процессор увеличивает RSI в RDI после обработки каждого элемента строки. Например, инструкции movs скопируют байт, слово, двойное слово или четверное слово из RSI в RDI, а затем увеличат RSI и RDI на 1, 2, 4 или 8 (соответственно). Если флаг направления установлен, x86-64 уменьшает RSI в RDI после обработки каждого элемента строки.

Для управления флагами направления применяются инструкции cld (сброс флага направления) и std (установка флага направления). Например, скопируем данные из одной строки в другую в противоположном направлении:

.globl _start

.data
nums1: .word 10, 11, 12, 13, 14, 15, 16, 17
len = . - nums1         # размер массива
count = len / elemSize         # количество элементов
elemSize = 2            # размер одного элемента
lastPosition = len - elemSize   # смещение последнего элемента относительно начала
nums2: .fill 8, 2, 0

.text
_start:
    movq $nums1, %rsi
    addq $lastPosition, %rsi  # указывает на последний элемент
    movq $nums2, %rdi
    addq $lastPosition, %rdi  # указывает на последний элемент
    movq $count, %rcx       # RCX = 8

    std         # устанавливаем флаг направления
    rep movsw

    movq $nums2, %rdi    
    movq 14(%rdi), %rdi  # проверяем 7-й элемент из nums2, он должен быть равен 17

    movq $60, %rax
    syscall

Здесь регистры RSI и RDI перед копированием строки указывают на последний элемент строки. И при вызове инструкции movsw сначала копируются последниие элементы строки, потом предпоследние и так далее вплоть до первого элемента.

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