结构体内存对齐

        在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。结构体的定义形式为:

struct 结构体名{
    结构体所包含的变量或数组
};

一、定义结构体变量

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。结构体变量才包含了实实在在的数据,需要内存空间来存储。

  • 方式一:先定义结构体,再定义结构体变量

//定义stu结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

//定义两个结构体变量
struct stu stu1, stu2;

stu为结构体名,里面包含name、num、age、group、score这5个成员。stu1stu2则为两个stu类型的结构体变量。

  • 方式二:在定义结构体的同时定义结构体变量

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1 stu2;

直接将变量放在结构体的最后即可。

如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。

  • 注意:在结构体内部定义结构体

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    struct sub1{
        char group;  //所在学习小组
    } sub1;
    struct sub2{
        float score;  //成绩
    };
};

如上所示,在stu结构体里还定义了『结构体变量sub1』和『结构体sub2』,由于sub2没有定义变量,所以其内部成员score即为母结构体stu的成员变量。

二、成员的获取和赋值

使用点号.获取结构体变量的单个成员,然后再进行赋值操作。

//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 135;

//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);

//打印结果
Tom的学号是12,年龄是18,在A组,今年的成绩是135!

也可以在定义结构体变量时整体赋值:

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };

三、结构体的内存分配

结构体中各成员在内存中是按顺序依次存储的,成员之间不互相影响,各占用不同的内存空间。结构体变量占用的内存大于等于所有成员占用的内存的总和,因为成员在存储时需要遵循结构体的内存对齐规则,成员之间可能会存在裂缝。

四、结构体内存对齐

结构体的内存对齐规则:

1. 数据成员对⻬规则:结构体的第⼀个数据成员会存放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始存储(⽐如int为4字节,则要从4的整数倍地址开始存储)。

2. 结构体作为成员:如果⼀个结构里有某些结构体成员,则结构体成员要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。(struct a⾥存有struct b,b⾥有char, int, double等元素,那b应该从8的整数倍开始存储.)

3. 总内存对齐:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员所占内存大小的整数倍,不⾜的要补⻬。若结构体a里包含结构体成员b,则需要将a的其他成员和b里的成员相比,得到最大成员内存,再按照最大内存的倍数进行补齐。

看完内存对齐规则是不是感觉有点绕?不急,接下来通过分析具体例子来理解这个规则。

示例1:含有多种数据类型成员

#pragma once
#include <iostream>
#include <stdio.h>
using namespace std;
struct Person {
    double a;   //8  内存地址下标序号:(0-7)
    char b;     //1 (8 - - -)
    int c;      //4 (12 13 14 15)
    short d;    //2 (16 17 - - - - - -)   //最终内存对齐为24
};

int main()
{
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person));    //24
}

输出结果分析:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • char b:b只占用1个字节内存,会按顺序存放在位置8处。
  • int c:c占用4字节内存,本该从位置9开始存放,但根据规则1可知,c必须从4的整数倍开始存储,而9不是4的整数倍,需要后移到位置12才开始存储,所以c存放在结构体内存中的12-15的位置。
  • short d:d占用2字节内存,可以接着从位置16开始存储,所以存放在16-17的位置。

根据上面的分析可知,Person的成员总共需要18字节内存,根据规则3,Person的内存大小必须是8(double a)的整数倍,所以最后内存大小为24。

sizeof:是一个运算符,用来计算传进来的数据类型占用多大的内存,在编译时即可完成运算。比如:sizeof(int)为4字节,sizeof(double)为8字节。

示例2:在示例一的基础上交换b、c成员位置

#pragma once
#include <iostream>
#include <stdio.h>
using namespace std;
struct Person {
    double a;   //8  内存地址下标序号:(0-7)
    int c;      //4 (8 9 10 11)
    char b;     //1 (12 - )
    short d;    //2 (14 15)   //最终内存对齐为16
};

int main()
{
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person));    //16
}

这次在示例1中Person的基础上交换了成员b和c的位置,输出结果就不一样了,分析如下:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • int c:c占用4字节内存,可以接着从位置8开始存储,所以c存放在结构体内存中的8-11的位置。
  • char b:b只占用1个字节内存,可以直接存放在位置12处。
  • short d:d占用2字节内存,本该从位置13开始存放,根据规则1可知,由于13不是2的倍数,需要后移到位置14开始存储,所以存放在14-15的位置。

根据上面的分析可知,Person的成员总共需要16字节内存,根据规则3,Person的内存大小必须是8(double a)的整数倍,所以最后内存大小为16。

示例3:结构体嵌套结构体

直接在Person里加上一个struct2成员,然后输出内存大小

#pragma once
#include <iostream>
#include <stdio.h>
using namespace std;

struct Person {        //示例一
    double a;   //8  内存地址下标序号:(0-7)
    char b;     //1 (8 - - -)
    int c;      //4 (12 13 14 15)
    short d;    //2 (16 17 - - - - - -)    24 这里要补够8个字节的理由是,总字节数是最大字节数成员的倍数
};

struct Person2 {     //示例一加示例一的大小
    double a;   //8  内存地址下标序号:(0-7)
    char b;     //1 (8 - - -)
    int c;      //4 (12 13 14 15)
    short d;    //2 (16 17 - - - - - -)    这里为什么要补够8个字节,因为下一个元素是结构体,最大成员字节数是8
    struct Person e; //struct Person占24个字节. 48是结构体内最大成员数8或double8的倍数
};


struct Person3 {        //示例二
    double a;   //8  内存地址下标序号:(0-7)
    int c;      //4 (8 9 10 11)
    char b;     //1 (12)
    short d;    //2 (14 15)   //最终内存对齐为16
};

struct Person4 {    //示例一加示例二的大小
    double a;
    char b;
    int c;
    short d; 
    struct Person3 e;   //16  24+16=40 ,40是8的倍数,已经对齐了
};

int main()
{
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person));    //24
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person2));    //48
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person3));    //16
    printf("sizeof(struct Person) = %lu \n", sizeof(struct Person4));    //40
}

五、面试题


六、参考


1. 

2. 哔哩哔哩 老秦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值