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/С++ немного отличаются. Он рассматривает вышеупомянутые две команды как эквивалентные, по существу игнорируя скобки вокруг операндов памяти.

Чтобы получить адрес переменной в 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 -размер, ax всегда WORD -размер, eax всегда DWORD -размер, а rax всегда QWORD -размер. Но это не помешает включить его в любом случае, если хотите, для согласованности с тем, как вы отмечаете операнды памяти.


Также, когда я пытаюсь сделать mov eax, [ebx], я получаю ошибку компиляции, почему это?

Эм... ты не должен. Это прекрасно подходит для меня в сборке MSVC. Как мы уже видели, это эквивалентно:

mov  eax, DWORD PTR [ebx]

и означает, что место памяти, на которое указывает ebx, будет разыменовано и что DWORD -размерное значение будет загружено в eax.


почему я не могу сделать mov a, [eax] Должен ли это не указывать указатель на то, куда указывает 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 перегружена большим количеством потенциальных значений в ISA x86. Это связано с корнями x86 как архитектуры CISC. Напротив, современные архитектуры RISC лучше выполняют разделение перемещений регистровых регистров, загрузок памяти и хранилищ памяти. x86 переписывает их все в одну инструкцию MOV. Слишком поздно возвращаться и исправлять его сейчас; вам просто нужно устроиться с синтаксисом, и иногда это занимает второй взгляд.