Типы данных в Ассемблере: определение переменных DB, DW, DD

Содержание

  1. 1.Типы данных в Ассемблере: зачем они нужны
  2. 2.DB в ассемблере: однобайтовые переменные
  3. 3.DW в ассемблере: двухбайтовые переменные
  4. 4.DD в ассемблере: четырёхбайтовые переменные
  5. 5.Порядок байтов: почему данные хранятся «задом наперёд»
  6. 6.Как выбрать директиву: DB, DW или DD
  7. 7.Инициализация переменных в сегменте данных
    1. 7.1.Неинициализированная память
    2. 7.2.Массивы и оператор DUP
  8. 8.Как ассемблер связывает имя переменной с адресом
  9. 9.Подытожим
Хотите заказать настройку и доработку сайта?
Эксперты Ворк24 помогут!

Начинающие программисты нередко спотыкаются уже на первых строках кода: непонятно, зачем объявлять переменные вручную, если в Python или C компилятор сам разбирается с размерами. Ассемблер так не работает — здесь каждый участок памяти выделяется явно и точно.

Разобраться с этим помогают три директивы: DB, DW и DD. Материал пригодится тем, кто пишет программы на ассемблере или изучает архитектуру x86 — от первокурсника до разработчика встраиваемых систем.

По данным учебно-методического портала Intel Software Developer Resources, работа с директивами резервирования данных составляет базу любой программы на ассемблере: без корректного объявления переменных невозможно ни разместить, ни прочитать данные в нужном месте.

Типы данных в Ассемблере: зачем они нужны

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

Никакой автоматики нет — если директива задаёт один размер, а команда ожидает другой, транслятор либо прервёт сборку с ошибкой, либо просто обрежет лишние биты без предупреждения.

На практике процессор x86 работает с тремя базовыми единицами: байт (8 бит), слово (16 бит) и двойное слово (32 бит). Именно под них созданы директивы DB, DW и DD. Каждая из них не просто «хранит число» — она указывает транслятору размер области и смещение переменной внутри сегмента.

Каждой переменной, объявленной через эти директивы, транслятор автоматически присваивает три характеристики: начальный адрес сегмента, смещение в байтах от его начала и тип — то есть объём выделенной памяти.

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

📍Обратите внимание!

Смещение переменной в сегменте данных вычисляется от нулевого адреса сегмента. Чем раньше объявлена переменная — тем меньше её offset.

DB в ассемблере: однобайтовые переменные

DB (Define Byte) резервирует ровно один байт — 8 бит. Диапазон беззнакового значения: 0…255; со знаком: –128…127. Директива применяется для хранения символов, флагов, коротких счётчиков и элементов строк.

Синтаксис прост:

имя DB значение

Несколько примеров из реального кода (MASM / TASM):

пример 1.png

Если значение неизвестно заранее — вместо числа ставят знак вопроса (DB ?). Тогда под переменную просто выделяется байт без инициализации.

Строки тоже объявляют через DB: каждый символ занимает отдельный байт. Признак конца строки — нулевой байт (0) или символы 13, 10 (перевод строки). Например: msg DB ‘Hello’, 13, 10, 0.

💡Заметьте!

Директива DUP ускоряет объявление массивов: вместо пятидесяти нулей пишут DB 50 DUP(0). Синтаксис: количество DUP(значение).

DW в ассемблере: двухбайтовые переменные

DW (Define Word) выделяет двухбайтовое слово — 16 бит. Беззнаковый диапазон: 0…65 535; знаковый: –32 768…32 767. Это стандартный размер для коротких целых чисел, смещений в 16-битных сегментах и счётчиков циклов в старых программах.

Примеры:

пример 2.png

Когда DW используется как операнд с символическим именем другой переменной, транслятор записывает в память только смещение (16-битный offset). Полный адрес (сегмент + смещение) можно получить лишь через DD. Этот нюанс важен при работе с указателями — путаница здесь приводит к доступу не по тому адресу.

Ещё одна особенность: ассемблер хранит многобайтовые числа в формате little-endian — младший байт первым. Значение 1000h (4096) для DW запишется в память как 00 10, а не 10 00. Это поведение стандартно для x86 и принципиально при разборе дампов памяти.

❗Путать DB и DW — одна из самых частых ошибок новичков. Если объявить переменную как DB, а потом загрузить её командой MOV AX, ассемблер выдаст ошибку типа несоответствия операндов.

DD в ассемблере: четырёхбайтовые переменные

DD (Define Double Word) резервирует четыре байта — 32 бита. Беззнаковый диапазон: 0…4 294 967 295; знаковый: –2 147 483 648…2 147 483 647. Это рабочий размер для 32-битных целых, адресов памяти и указателей в защищённом режиме x86.

Примеры объявления:

пример 3.png

Ключевое отличие DD от DW: при хранении адреса переменной DD записывает в память полный 32-битный адрес (или пару сегмент:смещение в 16-битном режиме). Именно поэтому в 32-битных программах под MASM32 указатели почти всегда объявляются через DD.

В современных компиляторах DD соответствует типу dword (MASM) или dd (NASM). Регистр букв не важен: DD, dd и Dd — одно и то же. Транслятор воспринимает их идентично.

Порядок байтов: почему данные хранятся «задом наперёд»

Когда вы записываете двух- или четырёхбайтовое значение в память, ассемблер x86 не кладёт байты в привычном «человеческом» порядке — от старшего к младшему. Архитектура Intel использует формат little-endian: первым в памяти стоит младший байт, последним — старший.

Разберём на конкретном числе. Допустим, вы объявляете переменную:

value DW 0x1234

В памяти она займёт два байта по смещениям, скажем, 0000h и 0001h. Вы можете ожидать последовательность 12 34 — старший байт сначала. Но на деле сегмент данных хранит её как 34 12: сначала 0x34 (младший), потом 0x12 (старший). Для DD картина та же — значение 0x12345678 ляжет в память как 78 56 34 12.

На первый взгляд это выглядит как неудобство. Но для процессора всё ровно наоборот: он читает число, начиная с адреса переменной, и первым забирает тот байт, что вносит наименьший вклад в итоговое значение. Это упрощает аппаратную логику сложения и инкремента — операции, которые всегда начинаются с младших разрядов.

На практике это не создаёт проблем, пока вы работаете с переменной целиком — загружаете её в регистр командой MOV и обрабатываете стандартными инструкциями. Трудности начинаются, когда вы вручную читаете дамп памяти или передаёте данные по сети в формате big-endian (сетевой порядок байтов — прямой). В таких случаях нужна явная перестановка байтов: в x86 для этого существует инструкция XCHG и более удобная BSWAP (для 32-битных данных).

Понимание little-endian особенно важно при отладке: если в дампе видите байты 78 56 34 12 — это не «мусор», а корректно сохранённое значение 0x12345678. Начинающие нередко принимают такую запись за повреждение данных и тратят часы на поиск несуществующей ошибки.

Как выбрать директиву: DB, DW или DD

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

Директива Расшифровка Размер Диапазон (беззнак.)
DB Define Byte 1 байт (8 бит) 0 … 255
DW Define Word 2 байта (16 бит) 0 … 65 535
DD Define Double Word 4 байта (32 бит) 0 … 4 294 967 295

Практическое правило: смотрите на регистр назначения. Команда MOV AL, val ожидает байт (AL — 8-битный). MOV AX, val — слово (AX — 16 бит). MOV EAX, val — двойное слово (EAX — 32 бит). Если размер переменной не совпадает с размером регистра, ассемблер сообщит об ошибке.

Второй ориентир — семантика данных:

  • Символы и строки → DB.
  • Короткие счётчики, смещения в 16-битном коде → DW.
  • 32-битные числа, адреса в защищённом режиме → DD.

Для ещё больших значений есть DQ (8 байт) и DT (10 байт), но они нужны значительно реже — в основном при работе с числами с плавающей точкой и 64-битными операндами. Директивы DB DW DD в ассемблере покрывают подавляющее большинство практических задач.

✅Если сомневаетесь между DW и DD — выбирайте DD. Лучше потратить лишние 2 байта памяти, чем получить переполнение при записи значения больше 65 535.

Инициализация переменных в сегменте данных

Переменные в ассемблере объявляют в специальном сегменте данных — секции .DATA (MASM) или section .data (NASM). Именно здесь транслятор выделяет память и выполняет инициализацию начальными значениями.

Минимальный пример сегмента данных на MASM:

пример 4.png

Переменные размещаются в памяти последовательно, одна за другой, точно в том порядке, в котором они объявлены. Первая переменная получает смещение 0000h, следующая — смещение, равное размеру предыдущей, и так далее.

Неинициализированная память

Знак ? вместо значения говорит транслятору: зарезервируй память, но ничего туда не пиши. Такое объявление допустимо для всех трёх директив: DB ?, DW ?, DD ?.

Реальное содержимое ячейки при старте программы непредсказуемо — это «мусор» из предыдущих данных. Поэтому если переменная должна начинать работу с конкретным значением, всегда задавайте его явно.

Массивы и оператор DUP

Для объявления массивов используют перечисление значений через запятую или оператор DUP:

пример 5.png

Обращение к элементу массива — через смещение. Если marks начинается с offset 0, то третий элемент (значение 30) лежит по адресу marks + 2, так как каждый элемент занимает один байт. Для DW шаг будет 2 байта, для DD — 4.

📌Закрепим!

В MASM переменные из секции .DATA? (неинициализированные) не попадают в исполняемый файл — они создаются в памяти только при запуске. Это уменьшает размер .exe.

Как ассемблер связывает имя переменной с адресом

Когда транслятор встречает объявление вроде counter DW 0, он не просто выделяет два байта. Он вносит имя counter в таблицу символов и фиксирует два параметра: номер сегмента и смещение внутри него. С этого момента каждый раз, когда имя counter встречается в коде, транслятор подставляет вместо него конкретный адрес — программист имени уже не видит, процессор работает только с числами.

Это принципиально отличается от языков высокого уровня, где переменная — абстракция с именем, типом и областью видимости. В ассемблере имя существует только на этапе трансляции. После сборки программы в исполняемый файл от символических имён не остаётся ничего — только адреса и значения в памяти. Именно поэтому отладка ассемблерного кода без специальных средств требует умения читать дампы и знания смещений вручную.

Смещение переменной внутри сегмента данных можно узнать оператором OFFSET. Выражение OFFSET counter вернёт числовое смещение переменной от начала сегмента — это полезно, когда нужно передать адрес переменной в регистр или сохранить его в другой переменной через DD. Полный сегментный адрес (пара сегмент:смещение) формируется уже на этапе загрузки программы операционной системой.

Важный практический момент: две переменные, объявленные подряд в секции .DATA, гарантированно лежат в памяти вплотную друг к другу. Между ними нет никаких промежутков, если вы сами их не создали. Это позволяет строить простые структуры данных — например, записи фиксированной длины — просто располагая поля одно за другим и обращаясь к ним через смещение от имени первого поля.

Подытожим

Директивы DB, DW и DD — это фундамент работы с данными в ассемблере x86. Каждая задаёт размер резервируемой области: байт, слово или двойное слово. Правильный выбор директивы исключает ошибки переполнения и несоответствия типов операндов.

Все три директивы работают по одному принципу: имя переменной → директива → значение (или ? для отложенной инициализации). Переменные последовательно размещаются в сегменте данных, получают смещение и становятся доступны по имени из любого места программы.

Когда разберётесь с базовыми директивами, следующий шаг — изучение адресации через смещение и работа с массивами. Это позволяет уже строить полноценные структуры данных прямо на уровне памяти — без лишних абстракций.

Вам нужна биржа фриланса для новичков или требуются разработчики сайтов?

Комментарии

Нет комментариев
Не можешь разобраться в этой теме?
Обратись за помощью к фрилансерам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 1 дня
Безопасная сделка
Прямой эфир