Указатели сборки x86
Надеюсь, это не глупый вопрос, но я пытаюсь сосредоточиться на указателях в сборке.
В чем именно разница между:
mov eax, ebx
а также
mov [eax], ebx
и когда должен dword ptr [eax]
должен быть использован?
Также, когда я пытаюсь сделать mov eax, [ebx]
Я получаю ошибку компиляции, почему это?
1 ответ
Как уже было сказано, заключение в скобки вокруг операнда означает, что этот операнд должен быть разыменован, как если бы он был указателем в C. Другими словами, скобки означают, что вы читаете значение из (или сохраняете значение в) это место в памяти, а не чтение этого значения напрямую.
Итак, это:
mov eax, ebx
просто копирует значение в ebx
в eax
, В псевдо-C нотации это будет: eax = ebx
,
Тогда как это:
mov eax, [ebx]
разыменовывает содержимое ebx
и сохраняет указанную стоимость в eax
, В псевдо-C нотации это будет: eax = *ebx
,
Наконец, это:
mov [eax], ebx
сохраняет значение в ebx
в ячейку памяти, на которую указывает eax
, Опять же, в псевдо-C нотации: *eax = ebx
,
Регистры здесь также могут быть заменены операндами памяти, такими как имена символических переменных. Итак, это:
mov eax, [myVar]
разыменовывает адрес переменной myVar
и хранит содержимое этой переменной в eax
, лайк eax = myVar
,
В отличие от этого:
mov eax, myVar
хранит адрес переменной myVar
в eax
, лайк eax = &myVar
,
По крайней мере, так работает большинство ассемблеров. Ассемблер Microsoft (называемый MASM) и встроенная сборка компилятора Microsoft C/C++ немного отличаются. Он рассматривает две вышеупомянутые инструкции как эквивалентные, по существу игнорируя скобки вокруг операндов памяти.
Чтобы получить адрес переменной в MASM, вы должны использовать OFFSET
ключевое слово:
mov eax, OFFSET myVar
Однако, даже несмотря на то, что MASM имеет этот прощающий синтаксис и позволяет вам быть небрежным, вы не должны этого делать. Всегда включайте скобки, когда вы хотите разыменовать переменную и получить ее фактическое значение. Вы никогда не получите неправильный результат, если напишите код явно, используя правильный синтаксис, и это облегчит другим понимание. Кроме того, это заставит вас привыкнуть писать код так, как его ожидают другие ассемблеры, а не полагаться на костыль MASM "делай то, что я имею в виду, а не то, что я пишу".
Говоря о том, что костыль "делай то, что я имею в виду, а не то, что я пишу", MASM также обычно позволяет вам избежать опускания спецификатора размера операнда, так как он знает размер переменной. Но опять же, я рекомендую написать это для ясности и последовательности. Следовательно, если myVar
является int
, вы бы сделали:
mov eax, DWORD PTR [myVar] ; eax = myVar
или же
mov DWORD PTR [myVar], eax ; myVar = eax
Эта нотация необходима в других ассемблерах, таких как NASM, которые не являются строго типизированными и не помнят этого myVar
это DWORD
размер памяти.
Это вообще не нужно при разыменовании регистровых операндов, так как имя регистра указывает на его размер. al
а также ah
всегда BYTE
-sized, ax
всегда WORD
-sized, eax
всегда DWORD
и rax
всегда QWORD
-sized. Но в любом случае не мешало бы включить его, если хотите, для согласованности с тем, как вы записываете операнды в памяти.
Также, когда я пытаюсь сделать
mov eax, [ebx]
Я получаю ошибку компиляции, почему это?
Хм... ты не должен. Это прекрасно для меня в сборке MSVC. Как мы уже видели, это эквивалентно:
mov eax, DWORD PTR [ebx]
и означает, что область памяти, указанная ebx
будет разыменовано и что DWORD
значение будет загружено в eax
,
почему я не могу сделать
mov a, [eax]
Разве это не делает "a" указателем на то, куда указывает eax?
Нет. Эта комбинация операндов не допускается. Как видно из документации к MOV
Инструкция, по сути, существует пять возможностей (игнорирование альтернативных кодировок и сегментов):
mov register, register ; copy one register to another
mov register, memory ; load value from memory into register
mov memory, register ; store value from register into memory
mov register, immediate ; move immediate value (constant) into register
mov memory, immediate ; store immediate value (constant) in memory
Обратите внимание, что нет mov memory, memory
, что вы пытались.
Тем не менее, вы можете сделать a
указать на то, что eax
указывает на простое кодирование:
mov DWORD PTR [a], eax
Сейчас a
а также eax
имеют одинаковое значение. Если eax
был указатель, то a
теперь указатель на ту же самую область памяти.
Если вы хотите установить a
к значению, которое eax
указывает на то, что вам нужно будет сделать:
mov eax, DWORD PTR [eax] ; eax = *eax
mov DWORD PTR [a], eax ; a = eax
Разумеется, это затирает указатель и заменяет его разыменованным значением. Если вы не хотите потерять указатель, вам придется использовать второй регистр "нуля"; что-то вроде:
mov edx, DWORD PTR [eax] ; edx = *eax
mov DWORD PTR [a], edx ; a = edx
Я понимаю, что все это несколько сбивает с толку. mov
Инструкция перегружена большим количеством потенциальных значений в x86 ISA. Это связано с корнями x86 как архитектуры CISC. Современные RISC-архитектуры, напротив, лучше справляются с разделением перемещений регистров-регистров, загрузок памяти и хранилищ памяти. х86 впихивает их всех в один mov
инструкция. Слишком поздно, чтобы вернуться и исправить это сейчас; Вы просто должны освоиться с синтаксисом, и иногда это занимает второй взгляд.