adding info about memory size for virtual functions

This commit is contained in:
Jidong Xiao
2025-04-15 08:13:36 -04:00
committed by JamesFlare1212
parent 63ddc1cc22
commit 8102db4dac
3 changed files with 223 additions and 0 deletions

View File

@@ -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 doesnt 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 doesnt 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.