39.结构体嵌套内存大小内存对齐

39.结构体-结构体嵌套内存大小内存对齐


39.1 知识点

以下是按照您的要求将内容转换成Markdown格式的结果:

结构体的嵌套使用

我们可以在一个结构体中包含结构体,甚至声明结构体。

struct Person
{
    string name = "韬老狮";
    int age = 18;

    struct Bag
    {
        int money = 10;
        string things[3];
    };

    Bag b = { 10, {"纸巾", "笔", "笔记本"} };
};

内部嵌套的结构体,我们也可以在外部使用它来声明对象。语法:外层结构体名::内层结构体名::内层结构体名…..(支持多层嵌套)

Person p = {};
cout << p.name << endl;
cout << p.age << endl;
cout << p.b.money << endl;
cout << p.b.things[0] << endl;
cout << p.b.things[1] << endl;
cout << p.b.things[2] << endl;

Person* p2 = new Person{};
cout << p2->name << endl;
cout << p2->age << endl;
cout << p2->b.money << endl;
cout << p2->b.things[0] << endl;
cout << p2->b.things[1] << endl;
cout << p2->b.things[2] << endl;

Person::Bag b = {};
cout << b.money << endl;

结构体的内存占用大小

使用 sizeof 可以有效地获取结构体的大小,包括成员变量和填充字节(为了满足特定的字节对齐要求)。由于内存对齐,结构体内存占用并不是将所有成员加起来的总和。

struct Student
{
    int age;    // 4  0~3
    int number; // 4  4~7
    bool sex;   // 1  8
    char n;     // 1  9  
    // 成员加起来是10个字节,整个结构体的大小是最大成员对齐要求的倍数。最大成员字节数是4。
    // 尾部填充2个字节,变为12字节 = 4 * 3
};
struct Student2
{
    double age; // 8  0~7
    int number; // 4  8~11
    bool sex;   // 1  12
    char n;     // 1  13
    // 成员加起来是14个字节,整个结构体的大小是最大成员对齐要求的倍数。最大成员字节数是8。
    // 尾部填充2个字节,变为16字节 = 8 * 2
};
struct Student3
{
    int number; // 4  0~3
    // 填充4~7,因为下一个double是8字节的,int后去填充4个字节。4~7只是用来填充,不会实际使用。
    double age; // 8  8~15
    bool sex;   // 1  16
    char n;     // 1  17
    // 成员加起来是18个字节,整个结构体的大小是最大成员对齐要求的倍数。最大成员字节数是8。
    // 尾部填充6个字节,变为24字节 = 8 * 3
};

什么是字节对齐(内存对齐)?

在计算机内存中,数据结构的起始地址需要满足特定的规则,以提高内存访问的效率。字节对齐确保数据在内存中的存储位置符合其类型的对齐要求,从而避免因不对齐访问而导致的性能损失或潜在的硬件异常。

每种数据类型通常都有一个对齐要求,即它在内存中存储时必须位于某个特定的字节边界上。比如:

  • int 可能要求4字节对齐
  • double 可能要求8字节对齐
  • char 通常要求1字节对齐

为了满足对齐要求,编译器会在结构体成员之间插入填充字节。这些填充字节不会被我们直接访问,但它们确保每个成员的地址都是对齐的。合理的字节对齐可以提高 CPU 访问内存的效率。现代 CPU 通常会在特定的字节边界上进行读取和写入操作,对齐访问通常比非对齐访问快得多。

对于结构体大小而言,它的字节占用大小通常是其最大成员对齐要求的倍数。编译器可能会在成员之间插入填充字节,以满足对齐要求。填充字节通常会放在结构体的成员之间或结构体的末尾。

规则:

  1. 每个成员变量的起始地址必须是其类型大小的倍数。
  2. 整个结构体的大小是最大成员对齐要求的倍数。
  3. 结构体嵌套时,外部结构体需要考虑内部结构体成员变量的字节最大数。比如,内部结构体最大的成员是8字节,外部结构体最大的成员是4字节,那么以8字节为准。

对于包含string的结构体:

struct A {
    char a;        // 1字节 0
    // 为了和double对齐,填充了7字节 1-7
    double b;      // 8字节 8-15
    int c;         // 4字节 16-19
    // 为了和double对齐,填充了4字节 20-23
    std::string str; // 40字节 24-63
    // 成员变量str,占用40个字节。
    // std::string是一个复杂的类,其大小可能因编译器和标准库实现而异。
    // 通常,它包含一个指向存储实际字符数据的堆内存的指针(可能是8字节)、
    // 存储字符串长度的size_t成员(可能是8字节)、存储字符串容量的size_t成员(可能是8字节),
    // 以及可能还有一些其他辅助成员或空间用于实现诸如短字符串优化等功能(例如16字节)。
    // 同时,为了确保str的内部成员(如指针和size_t)能从8字节对齐的位置开始存储,
    // 在c之后可能会有填充字节,因为std::string内部可能包含需要8字节对齐的成员。

    // 一共占用64字节
};

回顾不同变量的占用字节数:

cout << "int字节数:" << sizeof(int) << endl; // 4
cout << "double字节数:" << sizeof(double) << endl; // 8
cout << "bool字节数:" << sizeof(bool) << endl; // 1
cout << "char字节数:" << sizeof(char) << endl; // 1
cout << "string字节数:" << sizeof(string) << endl; // 40

打印不同结构体占用字节数:

cout << sizeof(Student) << endl; // 12 成员字节数加起来是10
cout << sizeof(Student2) << endl; // 16 成员字节数加起来是14
cout << sizeof(Student3) << endl; // 24 成员字节数加起来是14
cout << sizeof(A) << endl; // 64 成员字节数加起来是53

内存对齐带来的问题

内存对齐的目的是提高内存的访问性能,但它会带来浪费内存的问题。而且,通过举例我们发现,结构体中变量类型的不同顺序会带来不同的内存占用大小。因此我们应该按照以下的规则去排列变量:

  1. 按照大小降序排列成员,占用越大空间的变量类型放在前面。
  2. 减少不必要的成员变量。
  3. 使用更小的数据类型。

等等(有些手段以后再学)。


39.2 知识点代码

Lesson39_结构体_结构体嵌套内存大小内存对齐.cpp

#include <iostream>
using namespace std;

#pragma region 知识点一 结构体的嵌套使用

//1.我们可以在一个结构体中包含结构体 甚至声明结构体
struct Person
{
    string name = "韬老狮";
    int age = 18;

    struct Bag
    {
        int money = 10;
        string things[3];
    };
    Bag b = { 10, {"纸巾", "笔", "笔记本"} };
};

//2.内部嵌套的结构体,我们也可以在外部使用它来声明对象
//  语法:外层结构体名::内层结构体名::内层结构体名.....(支持多层嵌套)

#pragma endregion

#pragma region 知识点二 结构体的内存占用大小

//使用 sizeof 可以有效地获取结构体的大小,包括成员变量和填充字节(为了满足特定的字节对齐要求)
//由于内存对齐的远呀 结构体内存占用并不是把他所有成员加起来

struct Student
{
    int age;//4  0~3
    int number;//4  4~7
    bool sex;//1 8
    char n;//1 9  
    //成员加起来是10个字节 整个结构体的大小是最大成员对齐要求的倍数 最大成员字节数是4
    //尾部去填充2个字节 变为12字节 = 4 * 3
};

struct Student2
{
    double age;//8  0~7
    int number;//4  8~11
    bool sex;//1 12
    char n;//1 13
    //成员加起来是14个字节 整个结构体的大小是最大成员对齐要求的倍数 最大成员字节数是8
    //尾部去填充2个字节 16字节 = 8*2
};

struct Student3
{
    int number;//4 0~3
    //填充4~7 因为下一个double是8字节的 int后去填充4个字节 4~7 只是用来填充 不会实际使用
    double age;//8 8~15
    bool sex;//1 16
    char n;//1 17
    //成员加起来是18个字节 整个结构体的大小是最大成员对齐要求的倍数 最大成员字节数是8
    //尾部去填充6个字节 24字节 = 8 * 3
};

//什么是字节对齐(内存对齐) 
// 在计算机内存中,数据结构的起始地址需要满足特定的规则
// 以提高内存访问的效率
// 字节对齐确保数据在内存中的存储位置符合其类型的对齐要求
// 从而避免因不对齐访问而导致的性能损失或潜在的硬件异常
// 
// 每种数据类型通常都有一个对齐要求,即它在内存中存储时必须位于某个特定的字节边界上
// 比如:
//     int可能要求4字节对齐
//     double可能要求8字节对齐
//     char通常要求1字节对齐
// 为了满足对齐要求,编译器会在结构体成员之间插入填充字节。
// 这些填充字节不会被我们直接访问,但它们确保每个成员的地址都是对齐的
// 合理的字节对齐可以提高 CPU 访问内存的效率。
// 现代 CPU 通常会在特定的字节边界上进行读取和写入操作,对齐访问通常比非对齐访问快得多
// 
//对于结构体大小而言
//它的字节占用大小通常是其最大成员对齐要求的倍数
//编译器可能会在成员之间插入填充字节,以满足对齐要求
//填充字节通常会放在结构体的成员之间或结构体的末尾
//规则:
//1.每个成员变量的起始地址必须是其类型大小的倍数
//2.整个结构体的大小是最大成员对齐要求的倍数
//3.结构体嵌套时,外部结构体需要考虑内部结构体成员变量的字节最大数 
// 比如内部结构体最大的成员是8字节 外部最大的成员是4字节 那么以8字节为准


//对于包含string的结构体
struct A {
    char a;//1字节 0 
    //为了和double对齐填充了7字节 1-7
    double b;//8字节 8-15
    int c;//4 16-19
    //为了和double对齐填充了4字节 20-23
    std::string str;//40 24-63
    // 成员变量 str,占用 40 个字节。
    // std::string 是一个复杂的类,其大小可能因编译器和标准库实现而异。
    // 通常,它包含一个指向存储实际字符数据的堆内存的指针(可能是 8 字节)、
    // 存储字符串长度的 size_t 成员(可能是 8 字节)、存储字符串容量的 size_t 成员(可能是 8 字节),
    // 以及可能还有一些其他辅助成员或空间用于实现诸如短字符串优化等功能(例如 16 字节)。
    // 同时,为了确保 str 的内部成员(如指针和 size_t)能从 8 字节对齐的位置开始存储,
    // 在 c 之后可能会有填充字节,因为 std::string 内部可能包含需要 8 字节对齐的成员。

    //一共占用64字节
};

#pragma endregion

#pragma region 知识点三 内存对齐带来的问题

//内存对齐的目的是提高内存的访问性能
//但是会带来浪费内存的问题
//而且通过举例我们发现,结构体中变量类型的不同顺序会带来不同的内存占用大小
//因此我们应该按照以下的规则去排列变量
//1.按照大小降序排列成员,占用越大空间的变量类型放在前面
//2.减少不必要的成员变量
//3.使用更小的数据类型
//等等(有些手段以后再学)

#pragma endregion


int main()
{
    std::cout << "结构体嵌套、大小、内存对齐\n";

    #pragma region 知识点一 结构体的嵌套使用

    Person p = {};
    cout << p.name << endl;
    cout << p.age << endl;
    cout << p.b.money << endl;
    cout << p.b.things[0] << endl;
    cout << p.b.things[1] << endl;
    cout << p.b.things[2] << endl;

    Person* p2 = new Person{};
    cout << p2->name << endl;
    cout << p2->age << endl;
    cout << p2->b.money << endl;
    cout << p2->b.things[0] << endl;
    cout << p2->b.things[1] << endl;
    cout << p2->b.things[2] << endl;

    Person::Bag b = {};
    cout << b.money << endl;

    #pragma endregion

    #pragma region 知识点二 结构体的内存占用大小

    //回顾不同变量的占用字节数
    cout << "int字节数:" << sizeof(int) << endl;//4
    cout << "double字节数:" << sizeof(double) << endl;//8
    cout << "bool字节数:" << sizeof(bool) << endl;//1
    cout << "char字节数:" << sizeof(char) << endl;//1
    cout << "string字节数:" << sizeof(string) << endl;//40

    //打印不同结构体占用字节数
    cout << sizeof(Student) << endl;//12 成员字节数加起来是10
    cout << sizeof(Student2) << endl;//16 成员字节数加起来是14
    cout << sizeof(Student3) << endl;//24 成员字节数加起来是14
    cout << sizeof(A) << endl;//64 成员字节数加起来是53

    #pragma endregion
}

39.3 练习题

下面两个结构体大小是否相同?你写出分析流程


结构体A占多少字节,请写出分析流程




转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏