визуально Разрешено ли объекту юридически изменять свой тип при жизни в C ++?

У меня есть этот код:

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 изменился между вызовами или это просто недопущение оптимизации?

9

Решение

Почему 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 из приведенного выше списка.

9

Другие решения

Хранится в esi быть сохраненным между вызовами к различным функциям.

Конвенция Microsoft гласит

Компилятор генерирует пролог и эпилог-код для сохранения и восстановления регистров ESI, EDI, EBX и EBP, если они используются в функции.

поэтому указатель хранится в esi останется, но this указатель в ecx может не.

2

Чтобы ответить на вопрос из заголовка сначала:

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

Код в теле вопроса другой. Но, как правильно заметил Максим, у вас просто есть указатель. Этот указатель может указывать (в разное время) на два разных объекта, находящихся на одном и том же адресе.

2
По вопросам рекламы [email protected]