【结构体/Struct】结构体的字节对齐

一、结构体基础

        结构体是C语言中的用户自定义类型。结构体可以看作是一个集合,集合中包含了若干个成员变量,每个成员变量可以是不同的类型。结构的成员变量可以是常量、数组、指针甚至是其他结构体

        1、结构体的声明

//下面的结构体声明:声明了struct Books这个结构体变量,这个变量拥有三个成员变量,没有创建结构体变量
struct Books
{
    int page;
    char title[50];
    int id;
};
struct Books book1;//创建了一个结构体变量book1



//下面的结构体声明:声明了struct Books这个结构体变量,这个变量拥有三个成员变量,同时创建了两个结构体变量book1,book2
struct Books
{
    int page;
    char title[50];
    int id;
}book1,book2;

        2、结构体变量的初始化

struct Books book1 = { 300, "C Language", 153641};

        3、结构体成员变量的访问

//利用成员访问运算符'.'操作符进行访问
book1.page = 100;

//利用指针进行访问
struct Books* book_ptr;
book_ptr = &book1;
book_ptr->page = 100;

        4、使用typedef关键字

        typedef是C语言中的一个关键字,用于给变量取别名,当然也可以给结构体取别名。

typedef struct Books
{
    int page;
    char title[50];
    int id;
}Book;//注意大括号后的是你希望取的结构体别名

Book book1;    //现在可以用Book来创建结构体变量

二、结构体的字节对齐

        1、为什么需要字节对齐:空间换时间

       虽然内存当中每一个字节都存在一个地址,但是某些硬件平台对某些特定类型的数据只能从某些特定地址开始存取。设想某个硬件平台每次读都是从偶地址开始,如果某个int类型变量存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要读取两次,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。

        我们在写程序时一般不用考虑对齐问题,编译器会替用户选择对齐方式。当然,在某些内存十分稀缺的情况下,我们也可以通过改变默认对齐数减少内存的浪费

        2、对齐规则

        (1)结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。

        (2)其他成员变量要对⻬到 min{ 成员变量的大小,默认对齐数(VS 中默认的值为 8)} 的整数倍(0,1,2...)偏移量的地址处。

        (3) 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

        (4) 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。 

        这样看似乎有些抽象,结合例子:

 例1:

struct S1{
    char c1;    //对齐数1
    int i;      //对齐数4
    char c2;    //对齐数1
};
struct S1 s;

       

        结构体 s 的内存如上图所示。首先计算每个成员变量的对齐数,在未修改的情况下,VS中默认对齐数是8,对齐数是成员变量和默认对齐数取较小值,得到c1、i、c2的对齐数依次是1、4、1。

        假设 s 分配到的内存地址从200开始,根据对齐规则一,成员变量 c1 存储在偏移量为0的地方也就是内存开头。下一个变量 i,对齐数是4,所以根据规则2,i 应该存在最近的偏移量为4的整数倍的地址,显然是地址204,占用4个字节。最后一个变量 c2 ,对齐数是 1 ,c2 应该存在最近的偏移量为 1 的整数倍的地址,那么偏移量为8、9...都符合,那么就近放在偏移量为 8 的208地址。

        这个时候使用了9个字节的地址,但是别急,我们还需要检查规则3,结构体总⼤⼩为最⼤对⻬数的整数倍, 结构体 s 最大的成员变量对齐数是 4 ,那么结构体大小需要对齐到 12 。

例2:

struct S2{
    char c1;    //对齐数1 
    char c2;    //对齐数1
    int i;      //对齐数4
};
struct S2 s;

       

        在这个例子中,依然假设 s 分配到的内存地址从200开始,根据对齐规则一,成员变量 c1 存储在偏移量为0的地方也就是内存开头。下一个变量 c2,对齐数是1,所以根据规则2,i 应该存在最近的偏移量为1的整数倍的地址,显然是地址201,占用1个字节。最后一个变量 i,对齐数是 4 ,i 应该存在最近的偏移量为 4 的整数倍的地址,那么偏移量为 4 显然符合,那么就近放在偏移量为 4 的 204 地址。

        这个时候使用了8个字节的地址,检查规则3,结构体总⼤⼩为最⼤对⻬数的整数倍, 结构体 s 最大的成员变量对齐数是 4 ,那么结构体大小需要对齐到 8 ,满足条件。

        这个例子能看出,通过优先让占用内存小的成员变量靠近排布,可能可以节约内存。

例3:

struct S3
{
    double d;    //对齐数8
    char c;      //对齐数1
    int i;       //对齐数4
};

struct S4
{
    char c1;         //对齐数1
    struct S3 s3;    //对齐数8
    double d;        //对齐数8
};
struct S4 s;

              

 

        首先计算 S3 类型结构体的大小,对齐数依次为 8、1、4 ,最后的结果如上图所示,过程不再赘述。

        再来详细看看 S4 类型的结构体大小。与前面不同,结构体中嵌套了结构体,也就是要用到规则 4 。c1、d的对齐数分别是1、8,而结构体s3由于是嵌套的结构体,它的对齐数是由 s3 结构体内部的成员变量的最大对齐数决定的,而 s3 结构体内最大的对齐数是 8 ,所以 s3 要对齐到偏移量为 8 的整数倍的地址,而 s3 结构体的大小上面已经计算出是 16。d 的对齐数是8,刚好对齐到偏移量为 24 的地址。最后检查一下规则3,所有对⻬数中最⼤的为 8 ,此时 s4 的大小为32,符合规则 3 ,因此 S4 结构体的大小为 32 。

三、手动修改默认对齐数

               可以利用 #pragma pack(n) 这条指令修改默认对齐数,n是修改后的值,n为正整数。括号内不填参数时,恢复默认的对齐数。

#pragma pack(4)
struct S1{
    char c1;    //对齐数1
    int i;      //对齐数4
    char c2;    //对齐数1
};
#pragma pack()

        按照上面的格式写,可以仅修改 S1 结构体的对齐数,而不会影响后续定义的结构体。

#pragma pack(1)

        当默认对齐数修改为 1 时,每个成员变量的对齐数都会变成 1 ,成员变量之间将不会有内存浪费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值