adding info about memory size for virtual functions
This commit is contained in:
committed by
JamesFlare1212
parent
63ddc1cc22
commit
8102db4dac
@@ -206,3 +206,158 @@ int main() {
|
||||
## 26.7 Exercise
|
||||
|
||||
What is the output of the following [program](virtual.cpp)?
|
||||
|
||||
## 26.8 Memory Usage of Virtual Functions
|
||||
|
||||
Given the following [program](virtual2.cpp), what is the memory size of each class?
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
class Human {
|
||||
};
|
||||
|
||||
class Student {
|
||||
int age;
|
||||
};
|
||||
|
||||
class CollegeStudent {
|
||||
int age;
|
||||
void print(){
|
||||
std::cout << "I am a college student." << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
class CSStudent {
|
||||
int age;
|
||||
virtual void print(){
|
||||
std::cout << "I am a CS student." << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
int main(){
|
||||
std::cout << "memory size of Human class is: " << sizeof(Human) << std::endl;
|
||||
std::cout << "memory size of Student class is: " << sizeof(Student) << std::endl;
|
||||
std::cout << "memory size of College Student class is: " << sizeof(CollegeStudent) << std::endl;
|
||||
std::cout << "memory size of CS Student class is: " << sizeof(CSStudent) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 26.8.1 Empty Class
|
||||
|
||||
- An empty C++ class takes one byte because the C++ standard requires that each distinct object has a unique address in memory.
|
||||
|
||||
- If an empty class had size 0, then multiple instances of that class could end up having the same memory address, which would break basic assumptions in C++ like this:
|
||||
|
||||
```cpp
|
||||
class Empty {};
|
||||
|
||||
Empty a, b;
|
||||
std::cout << (&a == &b); // This should be false!
|
||||
```
|
||||
|
||||
- To make sure that &a != &b, the compiler gives each object at least one byte of storage, even if the class doesn’t contain any data.
|
||||
|
||||
### 26.8.2 Class CSStudent: Total size breakdown
|
||||
|
||||
- int age
|
||||
|
||||
Size: 4 bytes
|
||||
|
||||
- Virtual function (print)
|
||||
|
||||
- This makes the class polymorphic, so the compiler adds a vptr (virtual table pointer, also know as vtable pointer).
|
||||
|
||||
- On a 64-bit machine, a pointer is 8 bytes.
|
||||
|
||||
- Padding/alignment
|
||||
|
||||
- The compiler aligns data to certain boundaries for performance.
|
||||
|
||||
- Typical alignment for a class with a pointer is 8 bytes, so the 4-byte int is padded with 4 extra bytes.
|
||||
|
||||
### 26.8.3 Static Dispatch vs Dynamic Dispatch
|
||||
|
||||
- When you call a non-virtual member function like print() in the CollegeStudent class, the compiler resolves the call at compile time. This is known as static dispatch or early binding.
|
||||
|
||||
```cpp
|
||||
CollegeStudent alice;
|
||||
alice.print();
|
||||
```
|
||||
|
||||
Here's what happens under the hood:
|
||||
|
||||
- At compile time, the compiler sees that alice is of type CollegeStudent.
|
||||
|
||||
- It knows the exact location of CollegeStudent::print() in the compiled binary (it's in the .text segment).
|
||||
|
||||
- So it generates a direct call to that specific memory address. Like *call 0x123456* where *0x123456* is the address of the print() function.
|
||||
|
||||
- This is why the object doesn’t need to store any pointer to the function — the compiler already knows which function to call!
|
||||
|
||||
- If print() were marked virtual, like in the CSStudent class, then the call would become runtime-resolved using a vtable. Then:
|
||||
|
||||
- The object now gets a hidden pointer to a vtable (a lookup table of function pointers).
|
||||
|
||||
- When you call print(), the program:
|
||||
|
||||
- Looks up the function pointer in the vtable.
|
||||
|
||||
- Calls the function via that pointer.
|
||||
|
||||
- This is called dynamic dispatch or late binding.
|
||||
|
||||
Question: What if class CSStudent is defined as this, what would be the memory size of a CSStudent object?
|
||||
|
||||
```cpp
|
||||
class CSStudent {
|
||||
int age;
|
||||
virtual void print(){
|
||||
std::cout << "I am a CS student." << std::endl;
|
||||
}
|
||||
virtual void print2(){
|
||||
std::cout << "I am still a CS student." << std::endl;
|
||||
}
|
||||
virtual void print3(){
|
||||
std::cout << "I am still a CS student." << std::endl;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
To understand the problem, compile this [program](virtual3.cpp), and use the tool *pahole* to examine the memory information.
|
||||
|
||||
```console
|
||||
$ g++ -g virtual3.cpp
|
||||
$ pahole a.out
|
||||
class CSStudent {
|
||||
public:
|
||||
|
||||
void ~CSStudent(class CSStudent *, int);
|
||||
|
||||
void CSStudent(class CSStudent *, );
|
||||
|
||||
void CSStudent(class CSStudent *, const class CSStudent &);
|
||||
|
||||
void CSStudent(class CSStudent *);
|
||||
|
||||
int ()(void) * * _vptr.CSStudent; /* 0 8 */
|
||||
int age; /* 8 4 */
|
||||
virtual void print(class CSStudent *);
|
||||
|
||||
virtual void print2(class CSStudent *);
|
||||
|
||||
virtual void print3(class CSStudent *);
|
||||
|
||||
/* vtable has 3 entries: {
|
||||
[0] = print((null)),
|
||||
[1] = print2((null)),
|
||||
[2] = print3((null)),
|
||||
} */
|
||||
/* size: 16, cachelines: 1, members: 2 */
|
||||
/* padding: 4 */
|
||||
/* last cacheline: 16 bytes */
|
||||
};
|
||||
```
|
||||
|
||||
The comment /* 0 8 */ means that the virtual table pointer starts at offset 0 of the class, and it has 8 bytes; the comment /* 8 4 */ means the variable age starts at offset 8 and it has 4 bytes.
|
||||
|
||||
Reference in New Issue
Block a user