2012-09-18 17 views
9

tôi có mã này:Là một đối tượng được phép thay đổi hợp pháp loại của nó trong suốt cuộc đời của nó trong C++?

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

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

mà tôi biên dịch với Visual C++ 10 với/O2 và có tháo gỡ này:

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

Lưu ý rằng tại 00403968 địa chỉ của đầu đối tượng (trong đó vptr là được lưu trữ) được sao chép vào sổ đăng ký esi. Sau đó, tại 0040396E, địa chỉ này được sử dụng để truy xuất vptr và giá trị vptr được sử dụng để truy xuất địa chỉ của first(). Sau đó, tại 00403976, vptr được truy xuất lại và được sử dụng để truy xuất địa chỉ của second().

Tại sao vptr được truy lục hai lần? Có thể các đối tượng có thể có vptr của nó thay đổi giữa các cuộc gọi hoặc nó chỉ là một underoptimization?

+0

Lạ. Tôi nghĩ rằng tất cả nên là một no-op. Nó có thể là một sự thiếu tối ưu hóa, nếu nó nhìn vào bên trong các chức năng, nó có thể thấy rằng không ai trong số họ thay đổi 'esi'. –

Trả lời

9

Tại sao vptr được truy lục hai lần? Có thể đối tượng có thể có vptr của nó thay đổi giữa các cuộc gọi hoặc nó chỉ là một underoptimization?

xem xét:

object->first(); 

Cuộc gọi này có thể tiêu diệt các đối tượng và tạo một hình mới trong đoạn cùng bộ nhớ. Do đó, sau cuộc gọi này, không có giả thiết nào có thể được đưa ra về nhà nước. Ví dụ:

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

Trình biên dịch có thể tối ưu hóa tải đăng ký không cần thiết nếu sử dụng tính năng nội tuyến và/hoặc thời gian liên kết.


Vì DeadMG và Steve Jessop đã lưu ý mã trên thể hiện hành vi không xác định. Theo 3.8/7 của tiêu chuẩn C++ 2003:

Nếu, sau khi hết thời gian của đối tượng đã kết thúc và trước khi lưu trữ đối tượng bị sử dụng lại hoặc phát hành, đối tượng mới sẽ được tạo tại vị trí lưu trữ đối tượng ban đầu bị chiếm đóng, một con trỏ trỏ đến đối tượng gốc, một tham chiếu được gọi đến đối tượng ban đầu, hoặc tên của đối tượng gốc sẽ tự động tham chiếu đến đối tượng mới và, khi vòng đời của đối tượng mới đã bắt đầu, có thể được sử dụng để thao tác đối tượng mới, nếu:

  • bộ nhớ cho đối tượng mới chồng lên vị trí lưu trữ chính xác nơi đối tượng gốc chiếm giữ và
  • đối tượng mới có cùng loại với đối tượng ban đầu (bỏ qua vòng loại cv cấp cao nhất) và
  • loại đối tượng gốc không đủ điều kiện và nếu loại lớp không chứa bất kỳ thành viên dữ liệu không tĩnh nào có loại đủ điều kiện hoặc loại tham chiếu và
  • đối tượng gốc là đối tượng có nguồn gốc cao nhất (1.8) loại T và đối tượng mới là đối tượng có nguồn gốc T nhất nghĩa là, chúng không phải là các lớp con cấp cơ sở).

Mã trên không đáp ứng yêu cầu 2 từ danh sách ở trên.

+0

Tạo mã thời gian liên kết không giúp được gì - điều tương tự cũng xảy ra. –

+1

@LuchianGrigore: Có lẽ LTCG không trợ giúp * nhưng *, nhưng đó là một tối ưu hóa có sẵn cho các trình biên dịch tiềm năng trong tương lai. –

+0

@LuchianGrigore: gcc-4.7.1 tối ưu hóa mã gốc khi được đưa vào cuộc gọi tới 'operator new()' theo sau là một lệnh gọi tới 'operator delete()' với '-O3'. Bạn có quan tâm để cung cấp bất kỳ bằng chứng hỗ trợ cho bạn yêu cầu bồi thường? –

2

Nó được lưu trữ trong esi để được lưu giữa các cuộc gọi đến các chức năng khác nhau.

Quy ước Microsoft nói

Trình biên dịch tạo ra prolog và Epilog mã để lưu và khôi phục lại ESI, EDI, EBX, và thanh ghi EBP, nếu chúng được sử dụng trong hàm.

để con trỏ lưu trữ trong esi sẽ vẫn còn nhưng con trỏ this trong ecx có thể không.

+1

Ok, bắt đầu của đối tượng được lưu trữ trong 'esi', nhưng tại sao' vptr' đọc hai lần? – sharptooth

+0

Cũng giữ 'vptr' sẽ yêu cầu đăng ký bổ sung, như' edi', mà sau đó sẽ phải được lưu và khôi phục. Điều đó có cải thiện mã không? Khó nói. –

2

Để trả lời các câu hỏi từ những danh hiệu đầu tiên:

Vâng, một đối tượng từ một lớp học có nguồn gốc thay đổi kiểu của nó trong quá trình thi và hủy diệt. Đây là trường hợp duy nhất.

Mã trong phần nội dung của câu hỏi khác. Nhưng như Maxim ghi chú chính xác, bạn chỉ cần có một con trỏ. Con trỏ này có thể trỏ (vào các thời điểm khác nhau) cho hai đối tượng khác nhau cư trú tại cùng một địa chỉ.

Các vấn đề liên quan