2010-03-11 33 views
7

Khi chúng ta tạo một đối tượng của một lớp, bản đồ bộ nhớ trông như thế nào. Tôi quan tâm hơn đến cách đối tượng gọi các hàm thành viên không ảo. Liệu trình biên dịch tạo ra một bảng như vtable được chia sẻ giữa tất cả các đối tượng?Bản đồ bộ nhớ đối tượng lớp C++

class A 
{ 
public: 
    void f0() {} 
    int int_in_b1; 
}; 

A * a = new A; 

Bản đồ bộ nhớ của cái gì?

+4

Tôi đề nghị 'Bên trong Mô hình đối tượng C++' của Stanley Lippman nếu bạn muốn các đối tượng C++ có thể được mô hình hóa như thế nào (tôi nói có thể vì có nhiều cách để thực hiện nội bộ C++). –

+0

Nếu bạn sửa mã của mình, tại sao không chạy trình biên dịch của bạn với đầu ra của trình lắp ráp và xem nó tạo ra cái gì? –

Trả lời

12

Bạn có thể tưởng tượng được mã này:

struct A { 
    void f() {} 
    int int_in_b1; 
}; 

int main() { 
    A a; 
    a.f(); 
    return 0; 
} 

Bị biến thành một cái gì đó như:

struct A { 
    int int_in_b1; 
}; 
void A__f(A* const this) {} 

int main() { 
    A a; 
    A__f(&a); 
    return 0; 
} 

Calling f là chuyển tiếp thẳng vì nó không phải ảo. (Và đôi khi cho các cuộc gọi ảo, các công văn ảo có thể tránh được nếu các loại động của đối tượng được biết đến, vì nó là ở đây.)


Một ví dụ nữa mà một trong hai sẽ cung cấp cho bạn một ý tưởng về cách chức năng ảo làm việc hoặc khủng khiếp nhầm lẫn bạn:

struct B { 
    virtual void foo() { puts(__func__); } 
}; 
struct D : B { 
    virtual void foo() { puts(__func__); } 
}; 

int main() { 
    B* a[] = { new B(), new D() }; 
    a[0]->foo(); 
    a[1]->foo(); 
    return 0; 
} 

trở thành một cái gì đó như:

void B_foo(void) { puts(__func__); } 
void D_foo(void) { puts(__func__); } 

struct B_VT { 
    void (*foo)(void); 
} 
B_vtable = { B_foo }, 
D_vtable = { D_foo }; 

typedef struct B { 
    struct B_VT* vt; 
} B; 
B* new_B(void) { 
    B* p = malloc(sizeof(B)); 
    p->vt = &B_vtable; 
    return p; 
} 

typedef struct D { 
    struct B_VT* vt; 
} D; 
D* new_D(void) { 
    D* p = malloc(sizeof(D)); 
    p->vt = &D_vtable; 
    return p; 
} 

int main() { 
    B* a[] = {new_B(), new_D()}; 
    a[0]->vt->foo(); 
    a[1]->vt->foo(); 
    return 0; 
} 

Mỗi đối tượng chỉ có một con trỏ vtable, và bạn có thể thêm nhiều phương thức ảo vào lớp mà không ảnh hưởng đến kích thước đối tượng. (Vtable phát triển, nhưng điều này được lưu trữ một lần cho mỗi lớp và không phải là kích thước đáng kể.) Lưu ý rằng tôi đã đơn giản hóa nhiều chi tiết trong ví dụ này, nhưng nó does work: destructors không được giải quyết (mà nên thêm ảo ở đây), nó rò rỉ bộ nhớ và các giá trị __func__ sẽ hơi khác (chúng được tạo bởi trình biên dịch cho tên của hàm hiện tại), trong số các giá trị khác.

+0

Ví dụ thứ hai là một vài tuần tuổi mà tôi đã viết, và tôi thấy bây giờ mà tôi quên thêm * this * con trỏ, mặc dù chúng không được sử dụng. Nếu bạn không thấy cách thêm chúng, chỉ cần cho tôi biết và tôi có thể chỉnh sửa; nếu không tôi sẽ giữ nó giống như mã được biên dịch trong liên kết mã hóa. –

3

Nhận ra rằng ngôn ngữ C++ không chỉ định hoặc ủy nhiệm mọi thứ về bố cục bộ nhớ cho các đối tượng. Điều đó nói rằng, hầu hết các trình biên dịch làm điều đó khá giống nhau.

Trong ví dụ của bạn, đối tượng thuộc loại A chỉ yêu cầu đủ bộ nhớ để giữ int. Vì nó không có chức năng ảo nên nó không cần vtable. Nếu thành viên f0 đã được khai báo ảo thì đối tượng của loại A thường bắt đầu bằng một con trỏ tới lớp A vtable (được chia sẻ bởi tất cả các đối tượng thuộc loại A), sau đó là thành viên int.

Đổi lại, vtable có con trỏ tới từng hàm ảo, được xác định, kế thừa hoặc ghi đè. Gọi một hàm ảo cho một đối tượng bao gồm sau con trỏ tới vtable từ đối tượng, sau đó dùng offset cố định vào vtable (được xác định tại thời gian biên dịch cho mỗi hàm ảo) để tìm địa chỉ của hàm cần gọi.

+0

Tôi biết cách hoạt động của vtable. Tôi quan tâm đến cách trình biên dịch giao dịch với chức năng phi ảo. Có một bảng riêng biệt cho họ quá? – Bruce

+1

@Peter: Các hàm không có kích thước của lớp, và không mang về bố cục. Các hàm giống như bất kỳ hàm nào khác mà bạn viết, chúng nằm trong bộ nhớ ở đâu đó chờ đợi để được gọi. Điều duy nhất về các hàm thành viên là chúng có một con trỏ 'this' ẩn mà bạn không thấy. – GManNickG

+0

Vì vậy, khi tôi viết a.f0() làm thế nào để trình biên dịch nhận được địa chỉ của f0()? – Bruce

0
class A 
{ 
public: 
    void f0() {} 
    void f1(int x) {int_in_b1 = x; } 
    int int_in_b1; 
}; 

A *a = new A(); 

được nội thực hiện (đại diện) như thế này: (tên hàm thực sự đọc sai)

struct A 
{ 
    int int_in_b1; 
}; 

void Class_A__constructor(struct a*) {} // default constructor 
void Class_A__f0(struct a*) {} 
void Class_A__f1(struct a*, int x) {a->int_in_b1 = x;} 

// new is translated like this: (inline) 
void* new() { 
    void* addr = malloc(sizeof(struc a)); 
    Class_A__constructor(addr); 
    return addr; 
} 

Nó có thể được xác minh bằng cách thực hiện một lệnh "nm" trên tập tin đối tượng (kết quả với đọc sai tên)

+0

Bạn đã sao chép lỗi 'A a = new A();' từ câu hỏi. –

+0

@Roger: Cảm ơn bạn, tôi đã không chú ý – Phong

1

chức năng không được lưu trữ dựa trên lớp học của chúng.

thường trình biên dịch sẽ chỉ xử lý mọi chức năng thành viên giống như bất kỳ chức năng nào khác ngoại trừ thêm đối số cho con trỏ 'this'. được tự động chuyển đến hàm khi bạn gọi nó dựa trên địa chỉ của đối tượng mà nó được gọi.

tất cả các chức năng, thành viên tĩnh, thành viên hoặc thậm chí ảo được lưu trữ trong bộ nhớ theo cùng một cách, tất cả chỉ là chức năng.

khi trình biên dịch tạo mã nó khá nhiều mã cứng khi nó đi vào bộ nhớ, sau đó trình liên kết đi qua mã của bạn và thay thế lệnh "gọi hàm với tên này" bằng cách gọi hàm tại địa chỉ được mã hóa cứng này "

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