У меня есть этот код:
class Class {
public:
virtual void first() {};
virtual void second() {};
};
Class* object = new Class();
object->first();
object->second();
delete object;
что я скомпилирую с Visual C ++ 10 с / O2 и иметь эту разборку:
282: Class* object = new Class();
00403953 push 4
00403955 call dword ptr [__imp_operator new (4050BCh)]
0040395B add esp,4
0040395E test eax,eax
00403960 je wmain+1Ch (40396Ch)
00403962 mov dword ptr [eax],offset Class::`vftable' (4056A4h)
00403968 mov esi,eax
0040396A jmp wmain+1Eh (40396Eh)
0040396C xor esi,esi
283: object->first();
0040396E mov eax,dword ptr [esi]
00403970 mov edx,dword ptr [eax]
00403972 mov ecx,esi
00403974 call edx
284: object->second();
00403976 mov eax,dword ptr [esi]
00403978 mov edx,dword ptr [eax+4]
0040397B mov ecx,esi
0040397D call edx
285: delete object;
0040397F push esi
00403980 call dword ptr [__imp_operator delete (405138h)]
Обратите внимание, что в 00403968
адрес начала объекта (где vptr
хранится) копируется в esi
регистр. Тогда в 0040396E
этот адрес используется для получения vptr
и vptr
значение используется для получения адреса first()
, Тогда в 00403976
vptr
извлекается снова и используется для получения адреса second()
,
Почему vptr извлекается дважды? Может ли объект иметь свой vptr
изменился между вызовами или это просто недопущение оптимизации?
Почему vptr извлекается дважды? Может ли у объекта быть vptr измененным между вызовами, или это просто недероптимизация?
Рассматривать:
object->first();
Этот вызов может уничтожить объект и создать новый в той же части памяти. Следовательно, после этого звонка не может быть сделано никаких предположений о состоянии. Например.:
#include <new>
struct Class {
virtual void first();
virtual void second() {}
virtual ~Class() {}
};
struct OtherClass : Class {
void first() {}
void second() {}
};
void Class::first() {
void* p = this;
static_assert(sizeof(Class) == sizeof(OtherClass), "Oops");
this->~Class();
new (p) OtherClass;
}
int main() {
Class* object = new Class();
object->first();
object->second();
delete object;
}
Компиляторы могут оптимизировать ненужные загрузки регистров, если эта функция встроена и / или используется генерация кода времени соединения.
Как отметили DeadMG и Стив Джессоп, приведенный выше код демонстрирует неопределенное поведение. В соответствии с 3.8 / 7 стандарта C ++ 2003:
Если по истечении времени жизни объекта и до повторного использования или освобождения хранилища, которое занимал объект, в месте хранения, которое занимал исходный объект, создается новый объект, указатель, указывающий на исходный объект, ссылка, которая ссылка на исходный объект, или имя исходного объекта будет автоматически ссылаться на новый объект и после запуска времени жизни нового объекта может использоваться для манипулирования новым объектом, если:
- хранилище для нового объекта точно перекрывает место хранения, которое занимал исходный объект, и
- новый объект того же типа, что и исходный объект (без учета cv-квалификаторов верхнего уровня), и
- тип исходного объекта не является константно-квалифицированным, и, если тип класса, не содержит какого-либо нестатического члена данных, тип которого является константно-квалифицированным или ссылочным типом, и
- исходный объект был наиболее производным объектом (1.8) типа T, а новый объект — наиболее производным объектом типа T (то есть они не являются подобъектами базового класса).
Приведенный выше код не удовлетворяет требованию 2 из приведенного выше списка.
Хранится в esi
быть сохраненным между вызовами к различным функциям.
Конвенция Microsoft гласит
Компилятор генерирует пролог и эпилог-код для сохранения и восстановления регистров ESI, EDI, EBX и EBP, если они используются в функции.
поэтому указатель хранится в esi
останется, но this
указатель в ecx
может не.
Чтобы ответить на вопрос из заголовка сначала:
Да, объект из производного класса меняет свой тип во время создания и уничтожения. Это единственный случай.
Код в теле вопроса другой. Но, как правильно заметил Максим, у вас просто есть указатель. Этот указатель может указывать (в разное время) на два разных объекта, находящихся на одном и том же адресе.