我理解类实例成员的基本布局(给定一个典型的c ++实现),但是假设你有一个带有int num作为成员的MyClass,并且你创建了它的实例,该成员的特定地址如何在内存中在运行时处理? 我将通过一个例子更加清楚:
class MyClass
{
int num;
int num2;
int num3;
public:
void setNum(); //always sets num to 10
};
然后你调用setnum,它如何知道设置为10的内存? MyClass的内存布局可能类似于
class MyClass size(12):
+---
0 | num
4 | num1
8 | num2
+---
因此,当使用隐藏指向myclass实例的隐藏指针进行成员访问时,它是如此简单吗?它是基于offest编写的?例如myclasspointer + 4?
编辑澄清它是如何决定写入的?失败的copypaste离开了那里的vftable。我完全想象它只是一个已知的偏移对吗?
还是更复杂的东西?
为不明确的术语道歉我很少知道如何正确地表达一个问题...
答案 0 :(得分:2)
编译器将知道class
(或struct
)的内容,最重要的是知道不同成员变量的偏移量。 setNum
函数被赋予this
指针作为"隐藏"参数,编译器将采用this
变量并添加num
的偏移量。
究竟如何发生这取决于编译器。在LLVM中,它将使用getelementptr
VM指令,该指令理解结构,并且在给定基址的情况下,添加索引给出的偏移量。然后,这将转换为某种指令,它接受this
并在单个指令中添加直接偏移量,或者两条指令加载指针然后添加偏移量 - 取决于架构的一点点,以及接下来的说明是什么"需要"。
由于num
成员是结构中的第一个成员,它将为零,因此在使用clang ++ -O1编译的x86-64上,我们得到了这个反汇编:
_ZN7MyClass6setNumEv: # @_ZN7MyClass6setNumEv
movl $10, (%rdi)
retq
换句话说,将数字10移动到this
的地址中(在%rdi
- Linux机器上的第一个参数中)。
LLVM IR更好地展示了发生的事情:
%class.MyClass = type { i32, i32, i32 }
; Function Attrs: nounwind uwtable
define void @_ZN7MyClass6setNumEv(%class.MyClass* nocapture %this) #0 align 2 {
entry:
%num = getelementptr inbounds %class.MyClass* %this, i64 0, i32 0
store i32 10, i32* %num, align 4, !tbaa !1
ret void
}
该类包含3个i32
(32位整数),该函数采用this
指针,然后使用getelementptr
获取第一个元素(元素0)。是的,还有一个比你预期更多的争论。这就是LLVM的工作原理;)
然后将值10的商店指令转换为%num
计算的地址。
如果我们更改setNum
中的代码,以便将其存储到num2
,我们会得到:
define void @_ZN7MyClass6setNumEv(%class.MyClass* nocapture %this) #0 align 2 {
entry:
%num2 = getelementptr inbounds %class.MyClass* %this, i64 0, i32 2
store i32 10, i32* %num2, align 4, !tbaa !1
ret void
}
请注意将最后一个号码更改为getelementptr
。
作为汇编代码,它变为:
_ZN7MyClass6setNumEv: # @_ZN7MyClass6setNumEv
movl $10, 8(%rdi)
retq
(目前看来,在原始问题的修订版2中,您的类MyClass的大小为12,3 * 4字节,而不是像文本所说的那样8)。