C++对象内存布局探究

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 405人参与

目录

没有基类、没有虚函数类对象

单个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象

多个基类(基类没有虚函数、没有父类)、没有虚函数、没有虚继承的类对象

没有基类、有虚函数、没有虚继承的类对象

单个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象

多个基类(基类有虚函数、没有父类)、新增虚函数、没有虚继承的类对象

单基类(基类没有虚函数、没有父类)、没有虚函数、有虚继承的类对象

多基类(基类没有虚函数、有虚继承)、没有虚函数、没有虚继承的类对象

未完成,持续更新中...


看了一些讲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个基类成员变量] [自身成员变量] [虚基类成员变量]

未完成,持续更新中...

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值