2016-01-12 2 views
3

Ниже приводится код:В C++ почему адрес изменяется при преобразовании указателя?

#include <iostream> 
using namespace std; 

class B1 { 
public: 

    virtual void f1() { 
     cout << "B1\n"; 
     } 
}; 

class B2 { 
public: 

    virtual void f1() { 
     cout << "B2\n"; 
     } 
};   

class D : public B1, public B2 { 
public: 
    void f1() { 
     cout << "OK\n" ; 
    } 
}; 

int main() { 

D dd;  
B1 *b1d = &dd; 
B2 *b2d = &dd; 
D *ddd = &dd; 

cout << b1d << endl; 
cout << b2d << endl; 
cout << ddd << endl; 

b1d -> f1(); 
b2d -> f1(); 
ddd -> f1(); 
} 

Выход:

0x79ffdf842ee0 
0x79ffdf842ee8 
0x79ffdf842ee0 
OK 
OK 
OK 

Это выглядит запутанным для меня, потому что я ожидал b1d и b2d будет такой же, как оба они указывают на dd. Однако значение b1d и b2d отличается в зависимости от результата. Я думал, что это может быть связано с типом кастинга, но я не уверен, как это работает.

У кого-нибудь есть идеи об этом?

+2

[Это] (http://www.phpcompiler.org/articles/virtualinheritance.html) помог мне многое в прошлом. –

+0

Это вырожденный пример. – curiousguy

ответ

12

D наследует от B1 и B2.

С B1 наследуются от сначала B1 части объекта будет построена первым, а затем B2 часть объекта будет создана, тем D.

То, что вы видите, - это разница между тем, где эти части находятся в памяти, когда вы накладываете указатель производного типа на базовый тип.

b1d и ddd имеют тот же адрес, что и оба, указывающие на начало класса в памяти.

b2d является смещенным, так как указывает на начало B2 часть D.

+0

Я вижу. Является ли это стандартом C++ или это поведение, специфичное для компилятора? –

+0

@hanfeisun Это стандартное поведение. – NathanOliver

0

Стандарт C++ указывает, что размер объекта должен быть at least 1 (байт). Два отдельных объекта не могут иметь один и тот же адрес .

Подсобный объект может иметь тот же адрес, что и его содержащий объект. Обычно , ни один подобъект не может иметь тот же адрес, что и другой подобъект, потому что он не имеет прямого отношения. Поэтому (обычно) не более одного подобъекта может иметь тот же адрес, что и объект-контейнер.

В этом случае экземпляр D содержит 2 подобъекта. Они оба являются подобъектами базового класса. Один из них имеет тот же адрес, что и объект-контейнер, а другой нет.

Когда вы накладываете указатель производного типа на базовый тип, литой указатель укажет на подобъект базового класса. Нет ничего удивительного в том, что подобъекты имеют другой адрес. Также нет ничего удивительного в отношении одного из подобъектов, имеющих тот же адрес, что и контейнер.

Фактически существует исключение из правил в верхнем абзаце. Пустым подобъектам базового класса не нужно иметь никаких размеров. Это называется empty base optimization. Однако базовые классы не пустые, поэтому это не относится.

+0

Они не пустые, потому что каждый имеет указатель vtable. Стандарт не требует, чтобы эти указатели vtable существовали, но их требует практическая реализация. – JSF

+0

@JSF Ну, это деталь реализации, которая не касается stadard. В реализации, использующей vtable, пустая оптимизация базы здесь невозможна. Я должен проверить, как класс * empty * определен в стандарте. – user2079303

+0

@JSF Я не смог найти определение для пустого класса, но теперь я полагаю, что это означает «{};», а не класс без субобъектов, как я думал изначально. Таким образом, базы OP действительно не пусты этим определением. – user2079303

5

Ваше восприятие этого частично верно. Этот указатель относится к адресу объекта, который, конечно, является частью класса. Чтобы быть более формальным, это указатель на vtable этого класса. Но в случае, если вы наследуете несколько классов. Так к чему это нужно?

Скажем у вас есть это:

class concrete : public InterfaceA, public InterfaceB 

Конкретный объект, который наследует от обоих interfaceA и interfaceB должны быть в состоянии действовать, как если бы это был бот interfaceA и interfaceB (это точка общественности: когда вы наследовать). Поэтому должна быть «эта настройка», чтобы это можно было сделать.

Обычно в случае множественного наследования выбирается базовый класс (например, interfacea). В этом случае,

object layout

Практически каждый компилятор имеет «соглашение» для получения кода. Для того, чтобы позвонить в Фун, например, производятся сборка компилятора это что-то вроде:

call *(*objA+0) 

Где +0 части смещения функции внутри виртуальных таблиц.

Компилятор должен знать смещение этого метода (funa) во время компиляции.

Теперь, что произойдет, если вы хотите назвать funb? Основываясь на том, что мы сказали, funb должен быть в смещении 0 объекта interfaceB. Таким образом, есть thunk adjustor отрегулировать это так, что он указывает на таблицу виртуальных interfaceB, так что funB правильно можно назвать, снова:

call *(*objB+0) 

Если вы заявляете что-то вроде этого:

concrete *ac = new concrete(); 
interfaceB *ifb = ac; 

что делают вас ожидать? бетон в настоящее время играет роль interfaceB:

Если я правильно помню, вы можете печатать IFB и переменного тока (они являются указателями), и убедиться, что они указывают на разные адреса, но если вы проверить их на равенство:

ifb == ac; 

Вы должны быть правдивыми, потому что они настроены, чтобы изобразить, что они являются тем же самым динамически сгенерированным объектом.

object layout

+0

Спасибо ... Очень хорошо объяснил. :) – ashwin1103

Смежные вопросы