gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:41:33 24 4
gpt4 key购买 nike

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

我今天问了几个关于这个主题的问题,但我似乎无处可去。

我从第一个问题中学到了一些东西 - Object and struct member access and address offset calculation -我了解到现代处理器具有虚拟寻址功能,无需任何额外的算术周期即可计算内存偏移量。

从问题二 - Are address offsets resolved during compile time in C/C++? - 我了解到手动进行偏移时无法保证会发生这种情况。

现在应该很清楚,我想要实现的是利用硬件的虚拟内存寻址功能并从运行时卸载这些功能。

我正在使用 GCC,至于平台 - 我正在 Windows 中的 x86 上进行开发,但由于它是一个虚拟机,我希望它能在 GCC 支持的所有平台上高效运行。

欢迎提供有关该主题的任何信息,我们将不胜感激。

提前致谢!

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

编辑 2:这是虚拟机的一些伪代码:

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;

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

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

Case 3 是一个 goto 语句,指令指针增加了一个紧跟在 goto 指令之后的偏移量。

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

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

例如,这在 Java 中是如何完成的,其中只有虚拟机被编译为机器代码,这是否意味着对象地址的计算会因为运行时算法而导致性能下降?

最佳答案

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

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

X86 汇编中的指令在如何访问内存地址方面具有很大的灵 active 。读取或写入内存的指令具有根据以下公式计算的地址:

segment + base + index * size + offset

地址的段部分几乎总是默认的 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:

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

关于c++ - 如何在 C/C++ 中从运行时卸载内存偏移量计算?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11494379/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com