目录
单个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象
多个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象
单个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象
多个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象
单基类(基类没有虚函数、没有父类)、没有虚函数、有虚继承的类对象
多基类(基类没有虚函数、有虚继承)、没有虚函数、没有虚继承的类对象
看了一些讲C++对象内存布局的书,但还是不够直观,于是想写一些程序自己来验证和探究一下。
没有基类、没有虚函数类对象
测试程序
#include <cstdio>
#include <cassert>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(char c, int i): c_(c), i_(i) {}
char c_;
int i_;
};
int main() {
Base obj(0x11, 0x55443322);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
11 00 00 00 22 33 44 55
布局规律
这种类对象的内存中,类成员变量按照声明顺序依次排列,由于内存对齐的原因中间可能会出现一些填充。
单个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cassert>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(char c, int i): c_(c), i_(i) {}
char c_;
int i_;
};
class Derived: public Base {
public:
Derived(char c, int i, unsigned char d): Base(c, i), d_(d) {}
unsigned char d_;
};
int main() {
Derived obj(0x11, 0x55443322, 0xaa);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
11 00 00 00 22 33 44 55
AA 00 00 00
布局规律
这种类对象的内存中,先放置基类成员变量,再放置自身成员变量,成员变量按照声明顺序依次排列,由于内存对齐的原因中间可能会出现一些填充。其内存布局示意图如下:
[基类成员变量] [自身成员变量]
多个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cassert>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(char c, int i): c_(c), i_(i) {}
char c_;
int i_;
};
class Base1 {
public:
Base1(char c1): c1_(c1) {}
char c1_;
};
class Derived: public Base, public Base1 {
public:
Derived(char c, int i, unsigned char c1, unsigned char d): Base(c, i), Base1(c1), d_(d) {}
unsigned char d_;
};
int main() {
Derived obj(0x11, 0x55443322, 0xaa, 0xbb);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
11 00 00 00 22 33 44 55
AA BB 00 00
布局规律
这种类对象的内存中,先按照基类声明顺序放置各个基类成员变量,再放置自身成员变量,成员变量按照声明顺序依次排列,由于内存对齐的原因中间可能会出现一些填充。其内存布局示意图如下:
[基类1成员变量] [基类2成员变量] ... [基类N成员变量] [自身成员变量]
没有基类、有虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cassert>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(int val): val_(val) {}
virtual int value() { return val_; }
int val_;
};
int main() {
Base obj(0x11223344);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
18 09 40 00 00 00 00 00
44 33 22 11 00 00 00 00
布局规律
这种类对象的内存中,先放置虚指针,再按声明顺序放置成员变量,由于内存对齐的原因中间可能会出现一些填充。其内存布局示意图如下:
[vptr] [成员变量1] [成员变量2] ... [成员变量N]
单个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cassert>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(int val): val_(val) {}
virtual int value() { return val_; }
private:
int val_;
};
class Derived : public Base {
public:
Derived(int val, unsigned char ch): Base(val), ch_(ch) {}
virtual unsigned char character() { return ch_; }
private:
unsigned char ch_;
};
int main() {
Derived obj(0x11223344, 0xaa);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
A0 09 40 00 00 00 00 00
44 33 22 11 AA 00 00 00
布局规律
这种类对象的内存中,先放置虚指针,再放置基类成员变量,然后放置自身的成员变量,由于内存对齐的原因中间可能会出现一些填充。其内存布局示意图如下:
[vptr] [基类成员变量1] ... [基类成员变量N] [自身成员变量1] ... [自身成员变量N]
多个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cstring>
#include <cassert>
#include <iomanip>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
template<typename T>
void printMembFuncAddr(T funcPtr, const char *func_name) {
// 将成员函数指针转换为字节数组打印
unsigned char bytes[sizeof(funcPtr)];
std::memcpy(bytes, &funcPtr, sizeof(funcPtr));
std::cout << func_name << " address: ";
for(size_t i = 0; i < sizeof(funcPtr); ++i) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(bytes[i]) << " ";
}
std::cout << std::dec << std::endl;
}
class Base {
public:
Base(int val): val_(val) {}
virtual int value() { return val_; }
private:
int val_;
};
class Base1 {
public:
Base1(int val1): val1_(val1) {}
virtual int value1() { return val1_; }
private:
int val1_;
};
class Derived : public Base, public Base1 {
public:
Derived(int val, int val1, unsigned char ch): Base(val), Base1(val1), ch_(ch) {}
virtual unsigned char character() { return ch_; }
private:
unsigned char ch_;
};
// 添加虚函数表访问函数
void* getVTableAddress(const Base& obj) {
return *reinterpret_cast<void**>(const_cast<Base*>(&obj));
}
void* getVTableAddress(const Base1& obj) {
return *reinterpret_cast<void**>(const_cast<Base1*>(&obj));
}
int main() {
Derived obj(0x11223344, 0x55667788, 0xaa);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
cout << "\n验证虚函数表指针:\n";
// 验证Base部分的虚函数表指针
Base* basePtr = &obj;
void* baseVTable = getVTableAddress(*basePtr);
printf("Base vtable address: %p\n", baseVTable);
printf("内存中的第一个指针: 0x%08lX\n", *reinterpret_cast<unsigned long*>(&obj));
// 验证Base1部分的虚函数表指针
Base1* base1Ptr = &obj;
void* base1VTable = getVTableAddress(*base1Ptr);
printf("Base1 vtable address: %p\n", base1VTable);
// 计算Base1在Derived中的偏移量
Derived* derivedPtr = &obj;
Base1* base1InDerived = derivedPtr;
size_t offset = reinterpret_cast<char*>(base1InDerived) - reinterpret_cast<char*>(derivedPtr);
printf("Base1在Derived中的偏移量: %zu bytes\n", offset);
// 获取Base1在Derived对象中的虚函数表指针
void* base1VTableInDerived = *reinterpret_cast<void**>(reinterpret_cast<char*>(derivedPtr) + offset);
printf("Derived中Base1的vtable地址: %p\n", base1VTableInDerived);
return 0;
}
运行结果
由GCC编译
Object memory layout:
78 0B 40 00 00 00 00 00
44 33 22 11 00 00 00 00
98 0B 40 00 00 00 00 00
88 77 66 55 AA 00 00 00
验证虚函数表指针:
Base vtable address: 0x400b78
内存中的第一个指针: 0x00400B78
Base1 vtable address: 0x400b98
Base1在Derived中的偏移量: 16 bytes
Derived中Base1的vtable地址: 0x400b98
布局规律
这种类对象的内存中,先放置第一个基类和派生类共用虚函数表地址,再放置第一个基类成员变量,然后放置第二个基类的虚函数表地址,然后放置第二个基类的成员变量,以此类推,最后放置派生类自身的成员变量,由于内存对齐的原因中间可能会出现一些填充。其内存布局示意图如下:
[第1基类和派生类共用的虚函数表地址] [第1基类成员变量] [第2基类虚函数表地址] [第2基类成员变量] ... [第N基类虚函数表地址] [第N基类成员变量] [自身成员变量]
单基类(基类没有虚函数、没有父类)、没有虚函数、有虚继承的类对象
测试程序
#include <cstdio>
#include <cstring>
#include <cassert>
#include <iomanip>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(int val): val_(val) {}
private:
int val_;
};
class Derived1 : virtual public Base {
public:
Derived1(int val, unsigned char ch): Base(val), ch1_(ch) {}
private:
unsigned char ch1_;
};
int main() {
Derived1 obj(0x11223344, 0xee);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
由GCC编译
Object memory layout:
B0 09 40 00 00 00 00 00
EE 00 00 00 44 33 22 11
布局规律
这种类对象的内存中,先放置虚基类表地址,再放置派生类成员变量,最后放置虚基类成员变量。其内存布局示意图如下:
[虚基类表地址] [派生类成员变量] [虚基类成员变量]
多基类(基类没有虚函数、有虚继承)、没有虚函数、没有虚继承的类对象
测试程序
#include <cstdio>
#include <cstring>
#include <cassert>
#include <iomanip>
#include <iostream>
using namespace std;
/**
* @brief 严格固定2位十六进制打印任意对象的所有内存字节
* @tparam T 任意类型(基本类型、结构体、类、数组等)
* @param obj 目标对象(const引用,避免拷贝且不修改对象)
* @param lineWidth 每行字节数(默认8,支持自定义)
*/
template <typename T>
void printMemoryBytes(const T& obj, size_t lineWidth = 8) {
const unsigned char* pMem = reinterpret_cast<const unsigned char*>(&obj);
const size_t totalBytes = sizeof(T);
assert(lineWidth > 0 && "lineWidth must be greater than 0");
for (size_t i = 0; i < totalBytes; ++i) {
printf("%02X", static_cast<unsigned int>(pMem[i]));
if (i != totalBytes - 1 && (i + 1) % lineWidth != 0) {
printf(" ");
}
if ((i + 1) % lineWidth == 0) {
printf("\n");
}
}
if (totalBytes % lineWidth != 0) {
printf("\n");
}
}
class Base {
public:
Base(int val): val_(val) {}
private:
int val_;
};
class Derived1 : virtual public Base {
public:
Derived1(int val, unsigned char ch): Base(val), ch1_(ch) {}
private:
unsigned char ch1_;
};
class Derived2 : virtual public Base {
public:
Derived2(int val, unsigned char ch): Base(val), ch2_(ch) {}
private:
unsigned char ch2_;
};
class GrandSon : public Derived1, public Derived2 {
public:
GrandSon(int val, unsigned char ch1, unsigned char ch2, int val1)
: Base(val), Derived1(val, ch1), Derived2(val, ch2), val1_(val1) {}
private:
int val1_;
};
int main() {
GrandSon obj(0x11223344, 0xee, 0xff, 0x55667788);
cout << "Object memory layout:\n";
printMemoryBytes(obj);
return 0;
}
运行结果
Object memory layout:
98 0A 40 00 00 00 00 00
EE 09 40 00 00 00 00 00
B0 0A 40 00 00 00 00 00
FF 00 00 00 88 77 66 55
44 33 22 11 00 00 00 00
布局规律
这种类对象的内存中,先放置第1个基类的虚基类表地址,再放置第2个基类的成员变量,然后放置第2个基类的虚基类表地址,再放置第2个基类的成员变量,以此类推,然后放置自身成员变量,最后放置虚基类成员变量。其内存布局示意图如下:
[第1个基类虚基类表地址] [第1个基类成员变量] ... [第N个基类虚基类表地址] [第N个基类成员变量] [自身成员变量] [虚基类成员变量]
1026

被折叠的 条评论
为什么被折叠?



