如何在C/C++中从运行时卸载内存偏移量计算?

dte*_*ech 6 c c++ addressing virtual-memory offset

我正在实现一个简单的VM,目前我正在使用运行时算术来计算单个程序对象地址作为基指针的偏移量.

我今天就这个问题提出了几个问题,但我似乎无处可去.

我从第一个问题中学到了一些东西 - 对象和结构成员访问和地址偏移计算 - 我了解到现代处理器具有虚拟寻址功能,允许计算内存偏移,而无需任何额外的周期用于算术.

问题二 - 在C/C++编译期间是否解决了地址偏移? - 我了解到手动进行偏移时无法保证这种情况发生.

到目前为止,我应该清楚的是,我想要实现的是利用硬件的虚拟内存寻址功能并从运行时卸载它们.

我正在使用GCC,就像平台一样 - 我在Windows上开发x86,但由于它是一个VM,我希望它能够在GCC支持的所有平台上高效运行.

因此欢迎任何有关该主题的信息,我们将非常感谢.

提前致谢!

编辑:关于我的程序代码生成的一些概述 - 在设计阶段,程序被构建为树层次结构,然后递归地序列化为一个连续的内存块,以及索引对象并计算它们从程序内存块的开头的偏移量.

编辑2:这是VM的一些伪代码:

switch *instruction
   case 1: call_fn1(*(instruction+1)); instruction += (1+sizeof(parameter1)); break;
   case 2: call_fn2(*(instruction+1), *(instruction+1+sizeof(parameter1));
           instruction += (1+sizeof(parameter1)+sizeof(parameter2); break;
   case 3: instruction += *(instruction+1); break;  
Run Code Online (Sandbox Code Playgroud)

情况1是一个函数,它接受一个参数,该参数在指令之后立即找到,因此它作为指令的1个字节的偏移量传递.指令指针递增1 +第一个参数的大小以找到下一个指令.

情况2是一个函数,它接受两个参数,与之前相同,第一个参数作为1个字节的偏移量传递,第二个参数作为1个字节的偏移量加上第一个参数的大小.然后,指令指针增加指令的大小加上两个参数的大小.

情况3是goto语句,指令指针递增一个紧跟goto指令的偏移量.

编辑3:根据我的理解,操作系统将为每个进程提供自己专用的虚拟内存寻址空间.如果是这样,这是否意味着第一个地址总是......好零,所以从内存块的第一个字节开始的偏移实际上就是这个元素的地址?如果内存地址专用于每个进程,并且我知道程序存储器块的偏移量和每个程序对象与内存块的第一个字节的偏移量,那么在编译期间是否解析了对象地址?

问题是在编译C代码期间这些偏移是不可用的,它们在"编译"阶段和字节代码转换期间就已知.这是否意味着没有办法为"免费"进行对象内存地址计算?

例如,如何在Java中完成此操作,其中只将虚拟机编译为机器代码,这是否意味着由于运行时算术,对象地址的计算会降低性能?

Geo*_*edy 2

这里试图阐明链接的问题和答案如何适用于这种情况。

第一个问题的答案混合了两个不同的东西,第一个是 X86 指令中的寻址模式,第二个是虚拟到物理地址的映射。第一个是由编译器完成的,第二个是(通常)由操作系统设置的。就您而言,您应该只担心第一个。

X86 汇编中的指令在访问内存地址的方式上具有很大的灵活性。读取或写入存储器的指令的地址根据以下公式计算:

segment + base + index * size + offset
Run Code Online (Sandbox Code Playgroud)

地址的段部分几乎总是默认DS段,通常可以忽略。该base部分由通用寄存器之一或堆栈指针给出。该index部分由通用寄存器之一给出,大小为 1、2、4 或 8。最后,偏移量是嵌入在指令中的常量值。这些组件中的每一个都是可选的,但显然至少必须给出一个。

这种寻址能力通常指的是在没有显式算术指令的情况下计算地址。一位评论者提到了一条特殊指令:LEA它执行地址计算,但不是读取或写入内存,而是将计算出的地址存储在寄存器中。

对于问题中包含的代码,编译器很可能会使用这些寻址模式来避免显式算术指令。

例如,instruction变量的当前值可以保存在ESI寄存器中。此外, 和sizeof(parameter1)都是sizeof(parameter2)编译时间常数。在标准 X86 调用约定中,函数参数以相反的顺序推送(因此第一个参数位于堆栈顶部),因此汇编代码可能类似于

case1: 
  PUSH [ESI+1]
  CALL fn1
  ADD ESP,4 ; drop arguments from stack
  ADD ESI,5
  JMP end_switch
case2: 
  PUSH [ESI+5]
  PUSH [ESI+1]
  CALL fn2
  ADD ESP,8 ; drop arguments from stack
  ADD ESI,9
  JMP end_swtich
case3:
  MOV ESI,[ESI+1]
  JMP end_switch
end_switch:
Run Code Online (Sandbox Code Playgroud)

假设两个参数的大小均为 4 字节。当然,实际的代码取决于编译器,只要您要求进行某种程度的优化,编译器就会输出相当有效的代码是合理的。