是否允许对象在C ++的生命周期内合法更改其类型? [英] Is an object allowed to legally change its type during its lifetime in C++?

查看:150
本文介绍了是否允许对象在C ++的生命周期内合法更改其类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个代码:

  class Class {
public:
virtual void first {};
virtual void second(){};
};

Class * object = new Class();
object-> first();
object-> second();
删除对象;

我使用Visual C ++ 10与/ O2编译并具有反汇编:

  282:Class * object = new Class(); 
00403953 push 4
00403955 call dword ptr [__imp_operator new(4050BCh)]
0040395B添加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调用edx
284: > second();
00403976 mov eax,dword ptr [esi]
00403978 mov edx,dword ptr [eax + 4]
0040397B mov ecx,esi
0040397D调用edx
285:删除对象;
0040397F push esi
00403980 call dword ptr [__imp_operator delete(405138h)]

注意,在 00403968 将对象开始的地址(其中存储了 vptr )复制到 esi 寄存器。然后在 0040396E ,此地址用于检索 vptr vptr value用于检索 first()的地址。然后在 00403976 ,再次检索 vptr ,并用于检索 second )



为什么vptr被检索两次?

解决方案 vptr / div>


为什么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和Steve Jessop注意到上述代码显示未定义的行为。根据C ++ 2003标准的3.8 / 7:


如果在对象的生命周期结束之后,被重用或释放时,在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用于操纵新对象,如果:




  • 新对象会精确覆盖原始对象占用的存储位置,并且

  • 新对象与原始对象的类型相同(忽略顶级cv限定符),

  • 原始对象的类型不是const限定的,如果类类型不包含类型为const限定的任何非静态数据成员或引用类型,和

  • 原始对象是类型T的最多派生对象(1.8),而新对象是类型T的最多派生对象(即它们不是基类子对象)。


上述代码不符合上述列表中的要求2.


I have this code:

class Class {
public:
    virtual void first() {};
    virtual void second() {};
};

Class* object = new Class();
object->first();
object->second();
delete object;

that I compile with Visual C++ 10 with /O2 and have this disassembly:

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)]  

Note that at 00403968 the address of the object start (where vptr is stored) is copied into esi register. Then at 0040396E this address is used to retrieve the vptr and the vptr value is used to retrieve address of first(). Then at 00403976 the vptr is retrieved again and is used to retrieve the address of second().

Why is vptr retrieved twice? Could the object possible have its vptr changed in between calls or is it just an underoptimization?

解决方案

Why is vptr retrieved twice? Could the object possible have its vptr changed in between calls or is it just an underoptimization?

Consider:

object->first();

This call may destroy the object and create a new one in the same chunk of memory. Hence, after this call no assumptions can be made about the state. E.g.:

#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;
}

Compilers may optimize away unnecessary register loads if that function is inline and/or link-time code generation is used.


As DeadMG and Steve Jessop noted the above code exhibits undefined behaviour. According to 3.8/7 of C++ 2003 Standard:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

The above code doesn't satisfy requirement 2 from the above list.

这篇关于是否允许对象在C ++的生命周期内合法更改其类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆