Я пишу VM на C ++ для языка программирования. Язык является сборщиком мусора, поэтому у меня есть экземпляры классов C ++, которые размещены в куче сборщика мусора. Я использую копирующий коллектор, поэтому, когда происходит сборщик мусора, эти объекты перемещаются в память. Это означает, что каждый указатель на этот объект должен быть обновлен. Наиболее из этих указателей легко работать, кроме одного хитрого: 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
все еще указывает на старое неправильное местоположение.
Я мог бы обойти это, не используя методы экземпляров в классах, находящихся в управляемой памяти, но мне нравится, что код проще в этом смысле. Есть ли какие-то методы для борьбы с этим?
Ваш GC должен сканировать стек и регистрирует указатели и исправляет их. Если ваша виртуальная машина поддерживает многопоточность, вам нужно приостановить все потоки при сканировании их стеков. Указатель 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
если другой поток выполняет GC в любое время между первой и последней строками asm, как исправить r1?
Вы можете ввести другой уровень косвенности. Я буду использовать ваш пример:
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;
};
Обратите внимание, что каждая реализация управляемого будет делать это.