j'espère que ce n'est pas une question stupide, mais j'essaie de me concentrer sur les conseils à l'Assemblée.
Quelle est exactement la différence entre:
mov eax, ebx
et
mov [eax], ebx
et quand faut-il dword ptr [eax]
Devrait être utilisé?
Aussi quand j'essaye de faire mov eax, [ebx]
J'obtiens une erreur de compilation, pourquoi?
Comme cela a déjà été dit, le fait de placer des crochets autour d'un opérande signifie que cet opérande doit être déréférencé , comme s'il s'agissait d'un pointeur en C. En d'autres termes , les crochets signifient que vous lisez une valeur de (ou stockez une valeur dans ) cet emplacement de mémoire, plutôt que de lire directement cette valeur.
Donc ça:
mov eax, ebx
copie simplement la valeur de ebx
dans eax
. Dans une notation pseudo-C, ce serait: eax = ebx
.
Attendu que:
mov eax, [ebx]
déréférence le contenu de ebx
et stocke la valeur pointée dans eax
. Dans une notation pseudo-C, ce serait: eax = *ebx
.
Enfin, ceci:
mov [eax], ebx
stocke la valeur dans ebx
dans l'emplacement mémoire indiqué par eax
. Encore une fois, en notation pseudo-C: *eax = ebx
.
Les registres ici pourraient également être remplacés par des opérandes de mémoire, tels que des noms de variables symboliques. Donc ça:
mov eax, [myVar]
déréférence l'adresse de la variable myVar
et stocke le contenu de cette variable dans eax
, comme eax = myVar
.
En revanche, ceci:
mov eax, myVar
stocke l'adresse de la variable myVar
dans eax
, comme eax = &myVar
.
C'est du moins ainsi que la plupart des assembleurs fonctionnent. L'assembleur de Microsoft (appelé MASM) et l'assemblage en ligne du compilateur Microsoft C/C++ sont un peu différents. Il traite les deux instructions ci-dessus comme équivalentes, essentiellement en ignorant les crochets autour des opérandes de mémoire.
Pour obtenir l'adresse d'une variable dans MASM, vous devez utiliser le mot clé OFFSET
:
mov eax, OFFSET myVar
Cependant, même si MASM a cette syntaxe indulgente et vous permet d'être bâclé, vous ne devriez pas. Incluez toujours les crochets lorsque vous souhaitez déréférencer une variable et obtenir sa valeur réelle. Vous n'obtiendrez jamais le mauvais résultat si vous écrivez le code explicitement en utilisant la syntaxe appropriée, et cela facilitera la compréhension des autres. De plus, cela vous obligera à prendre l'habitude d'écrire le code de la façon dont les autres assembleurs s'attendront à ce qu'il soit écrit, plutôt que de compter sur la béquille "faites ce que je veux dire, pas ce que j'écris" de MASM.
En parlant de cette béquille "faites ce que je veux dire, pas ce que j'écris", MASM vous permet également généralement de vous passer de l'omission du spécificateur de taille d'opérande, car il connaît la taille de la variable. Mais encore une fois, je recommande de l'écrire pour plus de clarté et de cohérence. Par conséquent, si myVar
est un int
, vous feriez:
mov eax, DWORD PTR [myVar] ; eax = myVar
ou
mov DWORD PTR [myVar], eax ; myVar = eax
Cette notation est nécessaire dans d'autres assembleurs comme NASM qui ne sont pas fortement typés et ne se souviennent pas que myVar
est un emplacement mémoire de taille DWORD
.
Vous n'en avez pas du tout besoin pour déréférencer les opérandes de registre, car le nom du registre indique sa taille. al
et ah
sont toujours de taille BYTE
, ax
est toujours de taille Word
, eax
est toujours DWORD
- et rax
est toujours de taille QWORD
. Mais cela ne fait pas de mal de l'inclure de toute façon, si vous le souhaitez, par cohérence avec la façon dont vous notez les opérandes de la mémoire.
Aussi quand j'essaye de faire
mov eax, [ebx]
J'obtiens une erreur de compilation, pourquoi?
Tu ne devrais pas. Cela s'assemble très bien pour moi dans l'assemblage en ligne de MSVC. Comme nous l'avons déjà vu, cela équivaut à:
mov eax, DWORD PTR [ebx]
et signifie que l'emplacement de mémoire pointé par ebx
sera déréférencé et que la valeur de DWORD
sera chargée dans eax
.
pourquoi je ne peux pas faire
mov a, [eax]
Cela ne devrait-il pas faire de "a" un pointeur vers le pointage de eax?
Non. Cette combinaison d'opérandes n'est pas autorisée. Comme vous pouvez le voir dans la documentation de l'instruction MOV
, il existe essentiellement cinq possibilités (en ignorant les encodages et segments alternatifs):
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
Notez qu'il n'y a pas de mov memory, memory
, c'est ce que vous essayiez.
Cependant, vous pouvez faire pointer a
vers ce que pointe eax
en codant simplement:
mov DWORD PTR [a], eax
Maintenant, a
et eax
ont la même valeur. Si eax
était un pointeur, alors a
est maintenant un pointeur vers ce même emplacement mémoire.
Si vous souhaitez définir a
sur la valeur vers laquelle eax
pointe, alors vous devrez faire:
mov eax, DWORD PTR [eax] ; eax = *eax
mov DWORD PTR [a], eax ; a = eax
Bien sûr, cela encombre le pointeur et le remplace par la valeur déréférencée. Si vous ne voulez pas perdre le pointeur, vous devrez utiliser un deuxième registre "scratch"; quelque chose comme:
mov edx, DWORD PTR [eax] ; edx = *eax
mov DWORD PTR [a], edx ; a = edx
Je me rends compte que tout cela est quelque peu déroutant. L'instruction mov
est surchargée d'un grand nombre de significations potentielles dans l'ISA x86. Cela est dû aux racines de x86 en tant qu'architecture CISC. En revanche, les architectures RISC modernes font un meilleur travail de séparation des mouvements registre-registre, des charges de mémoire et des magasins de mémoire. x86 les rassemble en une seule instruction mov
. Il est trop tard pour revenir en arrière et le réparer maintenant; il suffit de se familiariser avec la syntaxe, et parfois cela prend un deuxième coup d'œil.