Массивы и строки в памяти

Урок показывает, как массивы и строки выглядят в памяти и как их обходить в ассемблере.

Массив в памяти — это непрерывный блок элементов одинакового размера, а его имя-метка указывает на первый элемент.

Массив — это просто адреса подряд

Объявим массив из четырёх 64-битных чисел в секции данных:

section .data
    arr dq 10, 20, 30, 40   ; четыре числа по 8 байт

Элементы лежат вплотную: arr по смещению 0, следующий по 8, затем 16, 24. Доступ к arr[i] — это адресация [arr + rcx*8], где rcx — индекс, а 8 — размер элемента.

    mov rcx, 2              ; индекс
    mov rax, [arr + rcx*8]  ; rax = arr[2] = 30

Строки и нулевой терминатор

В стиле C строка — это байты символов, заканчивающиеся нулевым байтом (0). Так функции узнают, где строка кончилась, не храня её длину отдельно:

section .data
    name db "Anna", 0   ; A n n a и завершающий 0

Чтобы найти длину, идут по байтам от начала, считая символы, пока не встретят ноль. Это в точности то, что делает strlen.

Как работает под капотом

Промоделируем подсчёт длины нуль-терминированной строки на Python — алгоритм один в один с ассемблером:

memory = [65, 110, 110, 97, 0]   # 'A','n','n','a', 0
length = 0
i = 0
while memory[i] != 0:    # пока байт не ноль
    length += 1
    i += 1
print("длина строки:", length)

Вывод:

длина строки: 4

Цикл просто шагает по памяти, пока не наткнётся на нулевой байт. Так же реальный код в ассемблере увеличивает индекс и проверяет cmp byte [str + rcx], 0. Именно поэтому забытый нулевой байт в строке C приводит к чтению «за край» — функция не понимает, где остановиться.

Частые ошибки

  • Неверный масштаб индекса. Для 8-байтных элементов масштаб 8, для байтовых — 1; ошибка ведёт в середину элемента.
  • Забыть нулевой байт в строке. Без него обход «улетит» за пределы строки в чужую память.
  • Выход за границы массива. В ассемблере никто не проверяет индекс — выход за край молча читает или портит соседнюю память.

Итог

  • Массив — непрерывный блок одинаковых элементов; имя указывает на первый.
  • Доступ к arr[i] — это адресация [arr + i*размер].
  • Строки в стиле C завершаются нулевым байтом, по нему ищут конец.
  • Границы массива никто не проверяет — за это отвечает программист.
Проверьте себя
1. Как обратиться к элементу arr[i] для массива 8-байтных чисел?
A[arr + i]
B[arr + i*8]
C[arr - i]
D[arr * i]
2. Как в стиле C определяют конец строки?
AПо первому пробелу
BПо нулевому байту (0) в конце
CПо длине, хранимой отдельно всегда
DПо переводу строки
3. Что произойдёт при выходе за границу массива в ассемблере?
AБудет ошибка компиляции
BПрограмма остановится с предупреждением
CНикто не проверяет границы — будет чтение/порча чужой памяти
DИндекс автоматически обрежется