2016-06-06 3 views
5

Я не понимаю одну вещь. Например, я объявляю класс А и класс В, который является потомком A:C++ динамические объекты. Как определяется размер объекта во время выполнения?

class A { 
    public: 
     int a; 
} 

class B : public A { 
    public: 
     int b; 
} 

Очевидно, что если я создаю экземпляры А или В, их размер в памяти можно определить по типу.

A instanceA; // size of this will probably be the size of int (property a) 
B instanceB; // size of this will probably be twice the size of int (properties a and b) 

Но что делать, если я создаю динамические экземпляры, а затем освобожу их позже?

A * instanceAPointer = new A(); 
A * instanceBPointer = new B(); 

Они являются экземплярами различных классов, но программа будет рассматривать их как экземпляры класса А. Это хорошо при их использовании, но что об освобождении их? Чтобы освободить выделенную память, программа должна знать размер свободной памяти, не так ли?

Так что, если я пишу

delete instanceAPointer; 
delete isntanceBPointer; 

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

Благодаря

+1

Возможно, эта ссылка поможет? http://www.openrce.org/articles/files/jangrayhood.pdf – OldProgrammer

+1

На самом деле, похоже, что второй может вызвать утечку памяти, поскольку это не полиморфный класс. Если класс является полиморфным, компилятор может освободить его на основе динамического типа, используя RTTI или какой-либо другой метод, и автоматически освобождает тот же объем памяти, который был фактически выделен. Если класс не является полиморфным, я не верю, что он сможет правильно обработать эту ситуацию, поэтому вы всегда должны удалять его с помощью указателя правильного типа. –

+0

@JustinTime По полиморфному классу вы имеете в виду класс с виртуальным деструктором? –

ответ

6

Я собираюсь предположить, что вы знаете, как delete works.

Как delete знает, как очистить унаследованный экземпляр. Вот почему вы используете virtual destructor в контексте наследования, иначе у вас будет неопределенное поведение. В принципе, деструктор, как и любая другая функция virtual, вызывается через vtable.

Напомним также, что: Компилятор C++ неявно Деструктор родительский класс (ы) в вашем деструкторе

class A { 
    public: 
     int a; 
    virtual ~A(){} 
} 

class B : public A { 
    public: 
     int b; 
    ~B() { /* The compiler will call ~A() at the very end of this scope */ } 
} 

Вот почему это будет работать;

A* a = new B(); 
delete a; 

С помощью vtable, деструктор ~B() будет вызываться delete. Поскольку компилятор неявно вставляет вызов деструктора базового класса (ов) в производный класс (ы), деструктор A будет вызываться в ~B().

+1

awesome, спасибо! –

+0

Так что, если я не объявляю виртуальный деструктор, программа может или не может произойти сбой в зависимости от реализации компилятора? –

3

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

С другой стороны, если у него есть виртуальный деструктор, тогда виртуальный диспетчерский механизм позаботится об освобождении правильного объема памяти за правильный адрес (т. Е. Для всего, самого производного объекта).

Вы можете найти адрес самого производного объекта самостоятельно, применив dynamic_cast<void*> к любому соответствующему указателю субобъектива базы. (См. Также this question.)

+3

Это правильно в отношении неопределенного поведения, но не корректно в отношении освобождения памяти. Число байтов, равное размеру реального объекта, всегда будет освобождено, даже без виртуального деструктора. Разумеется, правильный деструктор не будет называться. – SergeyA

+0

@SergeyA: Без виртуального деструктора вы даже не найдете правильный * адрес * для освобождения, а тем более размер ... –

+0

Kerrek, почему? Вы освобождаете весь блок памяти, размер известен как процедура разворачивания (обычно это префикс для блока), и вы начинаете с адреса, указанного в указателе. – SergeyA

2

Чтобы освободить выделенную память, программа должна знать размер свободной памяти, не так ли?

Если рассматривать библиотеку C malloc и free, вы увидите, что нет необходимости указывать объем памяти освобождается при вызове free, даже если в этом случае free снабжен void* так есть нет способа сделать это. Вместо этого библиотеки распределения обычно либо записывают, либо могут вывести достаточное количество информации о предоставленной памяти, так что одного указателя достаточно для освобождения.

Это верно с C++ Deallocation подпрограмм: если базовый класс обеспечивает свой собственный static void operator delete(void*, std::size_t)и базового класс деструктор virtual, то он будет принят размером динамического типа. По умолчанию освобождение заканчивается на ::operator delete(void*), которому не будет предоставлен какой-либо размер: сами процедуры распределения должны знать достаточно, чтобы работать.

Есть множество способов процедуры распределения могут работать, в том числе:

  • хранения размера из распределения

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

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