Как vtables реализованы в С++ и С#?

Возьмем такую ​​ситуацию (в С++, в С# классы A, B являются интерфейсами):

class A { virtual void func() = 0; };
class B { virtual void func() = 0; };
class X: public A, public B { virtual void func(){ var = 1; } int var;};

X * x = new X; // from what I know, x have 2 vtables, is this the same in c#?
A * a = (A*)x; // a == x
B * b = (B*)x; // here b != x, so when calling b->func(), how is the address of var correct?

Всегда ли компилятор С# создает одну виртуальную таблицу? Делает ли он какие-либо исправления указателя при литье?


person chris    schedule 03.09.2010    source источник
comment
Я полагаю, вы имеете в виду a == x и b != x. Поскольку указатели размещаются в стеке один за другим, все они имеют разные адреса... хотя все они должны указывать на один и тот же объект.   -  person PypeBros    schedule 03.09.2010
comment
конечно, моя ошибка :) Сейчас исправлено, я забыл, что a == b сравнивает адреса   -  person chris    schedule 03.09.2010


Ответы (4)


Чтобы не быть слишком педантичным, компилятор C# не вмешивается на этом уровне. Вся модель типов, наследование, реализация интерфейса и т. д. фактически обрабатываются CLR, а точнее CTS (Common Type System). Компиляторы .NET в основном просто генерируют код IL, который представляет намерение, которое позже выполняется CLR, где позаботятся обо всей обработке Vtable и т. Д.

Для получения подробной информации о том, как среда CLR создает типы среды выполнения и управляет ими, хорошей отправной точкой будет следующая ссылка. Ближе к концу объясняются таблица методов и карты интерфейса.

http://web.archive.org/web/20150515023057/https://msdn.microsoft.com/en-us/magazine/cc163791.aspx

person Chris Taylor    schedule 03.09.2010
comment
Эта ссылка сейчас не работает - она ​​просто ведет к указателю прошлых выпусков журнала MSDN. Чтобы просмотреть исходную статью, загрузите файл .CHM за май 2005 г., разблокируйте его, а затем перейдите к статье JIT и запуск: подробное изучение внутренних компонентов .NET Framework, чтобы увидеть, как среда CLR создает объекты среды выполнения. (В качестве альтернативы, погуглите заголовок.) Я бы отредактировал ссылку, но, похоже, официальной нет. - person Daniel McLaury; 07.02.2017
comment
Спасибо, заменил ссылку ссылкой на оригинальную статью через машину времени обратного пути. - person Chris Taylor; 08.02.2017

Если я изучу эту производную версию с помощью g++

class X: public A, public B { 
   unsigned magic;
 public:
   X() : magic(0xcafebabe) {};
   virtual void func(){ var = 1; } int var;
};

extern "C" int main() 
{
   X * x = new X; // from what I know, x have 2 vtables, is this the same in c#?
   A * a = (A*)x; // &a == &x
   B * b = (B*)x; // here &b != &x, so when calling b->func(), how is the address of var correct?
   printf("%p -- %p -- %p\n", x, a, b);

   unsigned* p = (unsigned*)((void*) x);
   unsigned *q = (unsigned*)(p[1]);
   printf("x=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]);
   p = (unsigned*)(p[0]);
   printf("a=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]);
   printf("b=[%x %x %x %x]\n",q[0],q[1],q[2],q[3]);

}

Оказывается, в C++ b == a+1, поэтому структура для X выглядит следующим образом: [vtable-X+A][vtable-B][magic][var] проверяет глубже (nm ./a.out), vtable -X+a содержит ссылку на X::func (как и следовало ожидать). когда вы преобразовали свой X в B, он скорректировал указатели так, чтобы функции VTBL для B появлялись там, где этого ожидает код.

Вы действительно намеревались «скрыть» B::func() ?

vtbl B выглядит как ссылка на «трамплин» на X, который восстанавливает указатель объекта на полный X перед вызовом «обычного» X::func, который содержит X+A vtbl.

080487ea <_ZThn8_N1X4funcEv>:   # in "X-B vtbl"
_ZThn8_N1X4funcEv():
 80487ea:       83 44 24 04 f8          addl   $0xfffffff8,0x4(%esp)
 80487ef:       eb 01                   jmp    80487f2 <_ZN1X4funcEv>
 80487f1:       90                      nop

080487f2 <_ZN1X4funcEv>:        # in X-A vtbl
_ZN1X4funcEv():
 80487f2:       55                      push   %ebp
 80487f3:       89 e5                   mov    %esp,%ebp
 80487f5:       8b 45 08                mov    0x8(%ebp),%eax
 80487f8:       c7 40 14 01 00 00 00    movl   $0x1,0x14(%eax)
 80487ff:       5d                      pop    %ebp
 8048800:       c3                      ret    
person PypeBros    schedule 03.09.2010
comment
Вы действительно только что extern "C" были главными? - person Cole Johnson; 05.01.2013
comment
Хм... не знаю. Похоже, это не то, что я обычно делаю в других моих инструментах C++. Это влияет только на изменение имени и внешнюю видимость, верно? - person PypeBros; 06.01.2013
comment
Здесь так весело)) - person rostamn739; 12.06.2016

Да, в управляемом языке существует только одна v-таблица, CLR не поддерживает множественное наследование. Существует исправление указателя при приведении к реализованному интерфейсу.

Это заметная проблема при попытке объявить COM-интерфейс, который сам объявлен из другого интерфейса, кроме IUnknown. Автор этой статьи не совсем понял эту проблему. Для COM требуется отдельная v-таблица для каждого интерфейса, как это делает компилятор, поддерживающий MI.

person Hans Passant    schedule 03.09.2010
comment
Если есть только одна виртуальная таблица, как С# поддерживает наследование алмазного интерфейса? - person chris; 03.09.2010
comment
@Chris, если вы посмотрите на ссылку, которую я предоставил в своем ответе, вы должны получить общее представление о том, как это работает. Вот цитата Дублирование слотов необходимо для создания иллюзии того, что у каждого интерфейса есть своя мини vtable. Однако дублированные слоты указывают на одну и ту же физическую реализацию. - person Chris Taylor; 03.09.2010
comment
Алмаз вызывает проблемы, потому что становится неясным, какую реализацию использовать. Интерфейс не имеет реализации. Может быть только один базовый класс. - person Hans Passant; 03.09.2010

vtables - это деталь реализации. Официальной/необходимой/ожидаемой реализации нет. Различные поставщики компиляторов могут по-разному реализовывать наследование.

person James Curran    schedule 03.09.2010