我正在用C ++编写一个用于编程语言的VM。语言是垃圾收集的,所以我有在垃圾收集堆中分配的C ++类实例。我正在使用复制收集器,因此当GC发生时,这些对象会在内存中移动。这意味着需要更新指向该对象的每个指针。 这些指针的大多数都很容易处理,除了一个棘手的问题:this
。考虑:
class SomeObj : public Managed // inheriting from this means it's on the GC heap
{
public:
void method()
{
SomeObj* other = new SomeObj(); // could trigger a GC.
printf("%d\n", someField); // this points to wrong memory
}
private:
int someField;
};
如果我位于GC堆上的某个对象的实例方法中间,则this
指向某个GC内存。集合可以在此方法的中间发生。发生这种情况时,对象将移动到新位置。但是,由于我们处于方法调用的中间,this
仍然指向旧的错误位置。
我可以通过不在托管内存中的类上使用实例方法来解决这个问题,但我确实认为代码更简单。有没有解决这个问题的技巧?
答案 0 :(得分:2)
您的GC需要扫描堆栈并注册指针并修复它们。如果您的VM支持多线程,则需要在扫描其堆栈时挂起所有线程。 'this'指针将位于堆栈或寄存器中。
由于C ++不提供堆栈的类型信息,因此您可能会遇到类似
的困难int i = 1000000;
char * p = new char[10]; // 0xF4240 = 1000000
无论你使用什么方法移动其他指针都会遇到同样的问题。在某些时候,你的代码必须将句柄转换为指针,这些指针需要修复。
像这样更改C ++代码
func()->method()
看起来像
struct GCroot call123 = { func() };
call123.obj->method();
多线程问题。如果您有这样的代码
struct GCroot obj123 = { /* .. */ };
obj123.ptr->x = obj123.ptr->x + 1;
它可能会生成像这样的伪汇编代码
load r1, obj123.ptr
load r2, (r1)
add r2, 1
store (r1), r2
如果另一个线程在第一个和最后一个asm行之间的任何时间执行GC,r1如何得到修复?
答案 1 :(得分:1)
您可以引入另一个级别的间接。我将使用你的例子:
class SomeData : public Managed
{
int someField;
};
class SomeObj : public Managed // inheriting from this means it's on the GC heap
{
public:
void method()
{
SomeObj* other = new SomeObj(); // could trigger a GC.
printf("%d\n", someData->someField); // this points to wrong memory
}
private:
SomeObjData* someData;
};
请注意,托管的每个实现都会这样做。