我正在用nasm编写一个简单的os,我是汇编的新手。从c开始,我习惯于声明指针而无需为它们保留内存并随意移动它们。我该怎么做?如果我声明一个变量例如通过
var: resb 1
我知道我声明了一个指针,并且可以访问变量的值,例如由
mov eax, [var]
尽管如此,我不能将指针移动到另一个地址
mov var, 0x1234
然后我得到“操作码和操作数的无效组合”。那么如何在内存中声明和移动指针?
答案 0 :(得分:2)
mov var, 0x1234
会给您一个错误,因为var
不是寻址模式,[var]
会是,但是,您仍然会收到错误消息,因为NASM无法分辨出操作。
mov DWORD [var], 0x1234
可以做到(假设您的内存模型使用32位Near指针)。
x86没有间接地址移动,而在C语言中,您可以“直接从内存中”使用指针(大致来说),在汇编中,您必须首先将指针加载到寄存器中,然后将该寄存器用作地址。
See for your self at Godbolt。
int* bar;
int foo()
{
return *bar;
}
------
foo(): # @foo()
mov eax, dword ptr [bar]
mov eax, dword ptr [eax]
ret
bar:
.long 0
deference操作需要一条额外的指令,这可能看起来像是使用了两次引用,但实际上并非如此(实际上,在NASM中,人们可以将变量的名称视为指向该变量的指针,但是让事情简单点)
您可以通过简单地将指针复制到寄存器中并将其传递(上面示例中的第一个mov
来做到这一点),将指针视为任何其他变量(包括对其进行复制)。
例如。
mov eax, DWORD [var]
call foo ;Call foo with the pointer in EAX
mov DWORD [var], 0x1234 ;Change the pointer value
关于保留内存:存储在内存中的任何变量都会占用一些空间,这就是程序状态的原因。
resb
用于未初始化的数据,这利用ELF功能来节省已编译二进制文件在磁盘上时的空间。
如果您要制作自己的操作系统,则可能根本不使用ELF,因此resb
(及类似名称)可能会因为分配零初始化的var而简单地回退(NASM会发出警告)。
如果范围有限,则可以使用堆栈临时存储您的var。这样可以通过限制内存占用空间来重复使用相同的空间。
另外,您可以使用%define
来定义程序集级符号,这些符号类似于(但完全不一样)C #define
s。
例如:
%define MY_PTR_TO_SOMETHING 0x1234
mov DWORD [MY_PTR_TO_SOMETHING], 1 ;In C this is *MY_PTR_TO_SOMETHING = 1;
这不会为MY_PTR_TO_SOMETHING
分配空间,因为它只是数字0x1234
的别名。您可以将其视为C #define MY_PTR_TO_SOMETHING ((int*)0x1234)
。或像static const int *MY_PTR_TO_SOMETHING = (int*)0x1234;
那样使用编译器,它确实为指针对象本身优化了实际的静态存储。
但是请注意,由于指针值现在可以作为已知常量使用了,因此间接级别已消失。
当然,您仍然可以通过它:
mov eax, MY_PTR_TO_SOMETHING ;EAX = Holds the value of MY_PTR_TO_SOMETHING
mov ebx, DWORD [eax] ;EBX = Load a DWORD from the address 0x1234
;Store or copy the EAX register to pass the pointer around
如果不习惯使用x86寻址模式,使用指针可能会造成混淆。我的建议是阅读mov
的说明手册,并确保了解mov eax, 0x1234
和mov eax, DWORD [0x1234]
之间的区别。
如果需要一个指针,可以在运行时进行修改,则需要为指针值存储一些空间;它不能是汇编时间常量或标签地址。
在C语言中,您将使用int *ptr;
来为指针大小的对象在静态存储(在全局范围内)或在寄存器(或堆栈空间)中为本地范围保留空间。