From 8102db4dacc63d6130ce406b2168cb01d720fdb4 Mon Sep 17 00:00:00 2001 From: Jidong Xiao Date: Tue, 15 Apr 2025 08:13:36 -0400 Subject: [PATCH] adding info about memory size for virtual functions --- lectures/26_inheritance_II/README.md | 155 ++++++++++++++++++++++++ lectures/26_inheritance_II/virtual2.cpp | 31 +++++ lectures/26_inheritance_II/virtual3.cpp | 37 ++++++ 3 files changed, 223 insertions(+) create mode 100644 lectures/26_inheritance_II/virtual2.cpp create mode 100644 lectures/26_inheritance_II/virtual3.cpp diff --git a/lectures/26_inheritance_II/README.md b/lectures/26_inheritance_II/README.md index f462b08..36fbc97 100644 --- a/lectures/26_inheritance_II/README.md +++ b/lectures/26_inheritance_II/README.md @@ -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 + +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. diff --git a/lectures/26_inheritance_II/virtual2.cpp b/lectures/26_inheritance_II/virtual2.cpp new file mode 100644 index 0000000..b7bbfd5 --- /dev/null +++ b/lectures/26_inheritance_II/virtual2.cpp @@ -0,0 +1,31 @@ +#include + +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(){ + CSStudent cs; + 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; +} diff --git a/lectures/26_inheritance_II/virtual3.cpp b/lectures/26_inheritance_II/virtual3.cpp new file mode 100644 index 0000000..23f0a51 --- /dev/null +++ b/lectures/26_inheritance_II/virtual3.cpp @@ -0,0 +1,37 @@ +#include + +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; + } + 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; + } +}; + +int main(){ + CSStudent cs; + 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; +}