结构体PLUS(计算结构体类型大小及位段)~~


关于结构体类型,之前阿涛写过一篇文章,里面就重点讲了结构体类型的基本使用,但仅仅会创建,初始化,传参结构体是远远不够的,就比如整型变量是四个字节,double变量是八个字节,那结构体变量的大小到底何如判断呢?
还有更多的问题,这篇博客都会为您讲解!

1. 结构体内存对齐

那什么是结构体内存对齐呢?这里面就计算的角度我们只需要知道,内存对齐是为了方便我们方便计算机计算结构体大小!

int main()
{
    struct S1
    {
        char c1;
        int i;
        char c2;
    };
    printf("%d\n", sizeof(struct S1));
    struct S2
    {
        char c1;
        char c2;
        int i;
    };
    printf("%d\n", sizeof(struct S2));
    struct S3
    {
        double d;
        char c;
        int i;
    };
    printf("%d\n", sizeof(struct S3));
    return 0;
}
             

这里我们用sizeof计算了三个结构体的大小,那结构体大小是不是就是简简单单把结构体成员的大小都加起来这么简单呢?
结构是不是6,6,13呢?

在这里插入图片描述
好家伙这是一个都不中啊!
那接下来我们就好好讲解一番结构体的内存对齐:
首先我们假设我们创建了一个结构体变量:

    struct S1
    {
        char c1;
        int i;
        char c2;
    };

在这里插入图片描述
那么根据我们已经掌握的知识:创建变量,就是向系统申请一定的空间,那我们计算大小就从申请的首字节开始计算,也就是箭头指向的那个位置记为偏移起始位置为零的位置!
而我们结构体变量就是为了存放结构体成员而存在的,所以我们第一个结构体成员无论大小都会从偏移为零的位置开始占用自己大小的内存,一个单元就是一个字节。
在这里插入图片描述
这就是我们第一个char变量的存放。
那第二个int变量我们应该如何处理呢?
这里就要讲到对齐数的概念了:
除了第一个成员, 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8。

当然了上面我们说的都是以字节为单位计算的。
那我们int类型的成员大小是4,是小于8的,也就是要对齐到4的倍数的偏移量处,而我们第一个成员已经占掉了偏移量为零的一个字节,那是不是零之后的三个字节我们就不能使用了,直接跳转到偏移量为4处!!
在这里插入图片描述
而我们的int类型本身就是攒占了四个字节的,从偏移量为四一直到七就都被我们的int占用了!
在这里插入图片描述
最后我们的那个char类型的成员大小为1小于默认对齐数8,那对齐数取其小就是1,我们就接着存放就行。
到目前为止我们的成员已经完全存放好了,现在到了偏移量为8的地步,也就是总共占了九个字节内存,那为啥我们最后打印出来的大小是12呢?
原来我们的内存对齐还有一条明文规定:
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
我们把每个成员变量的对齐数都列出来然后取其大,我们最终的内存大小就应该是这个最大对齐数的倍数,在我们这道题目里面也就是4的倍数,九显然不是4的倍数,因此我们还要往后面再占用三个字节大小,不使用就是玩儿~~~
在这里插入图片描述
这样一来我们就到达了偏移量为十一的位置,内存大小总计也就来到了12,完成了我们这一次的计算!
我自认为在学习这部分知识的时候只要掌握了就不会忘记了,只是这里有一些难以区别?就是我们对齐数是按照偏移量来算的,而偏移量总是比我们已经占用的内存大小小一,但是我们最后在计算结构体大小的时候又不能看偏移量,还必须是计算总的内存大小,这就容易搞混!
那之前我们也说过结构体是不可以嵌套一个自己类型的结构体的,这样子计算结构体大小都成了套娃!但是我们可以嵌套一个别的类型的结构体啊!

//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));

就类似于这样的,我们该怎么办呢?
不要苦恼!前人之述备矣!
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。也就是说我们还是要埋下头来,看嵌套的那个结构体的情况,这里的对齐数不是嵌套的结构体总的大小和默认对齐数的较小值,而是嵌套结构体中每个成员的对齐数的最大值,兄弟们应该是能够理解我想表达的意思的吧!!

为什么要内存对齐呢?

那我们废了这么大的力气来搞这个内存对齐总不至于是闹着玩吧?
大体上有两种原因:
1.平台原因:
有些平台啊是不可以随意访问任意位置上的数据的,就好比它就是个定点射手,它就只能处理到这一系列位置上的球,别的点的球它接不到!
2.性能原因:
我们假设现在内存是不对齐的,那所有的数据是不是就堆在了一起?那是不是有可能我们在访问内存的时候有可能就访问到了这个变量后面的内容,那对于我们后面的这个变量来说我们只拿到了一部分的数据,这哪行呢?所以我们还要额外再对它进行一次访问,才有可能拿到完整的数据,这也就导致我们性能的下降!
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们有没有办法既对齐,又尽可能地节省空间呢?
我们会浪费空间关键就在于我们对齐数大的话,它每两个相邻的对齐数之间的空位置就很多,那我们的办法就是让占用空间小的成员尽量集中一点,这样就不会因为大对齐数的成员之间因为一个小小对齐数而偏移过多!
我们上面的两个例子也很好说明了这一点:
在这里插入图片描述

2.修改默认对齐数

那么刚才我们说了,默认的对齐数为8,那我们就是桀骜不驯,我们程序员就是有很大的自主能力,我们就是不喜欢默认的,我们就要自己设置可不可以?
当然可以啊!
#pragma 这是个预处理指令,我们第一次接触到的预处理指令应该还是 #define#pragma pack(8) 的作用就是修改默认对齐数为8,
( )里面什么都不加就是还原默认修改数!
我们可以根据自己的需求来设置默认对齐数,但是一般来说设置的都是2的次方。

3.位段

关于结构体大小的内容我们已经讲完了,这里又加上一个位段,单单只是因为我觉得位段和结构体大小有着千丝万缕的关系……

什么是位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。

struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

那我们先来感受一下位段的大小:
在这里插入图片描述
好像是跟我们刚才讲的结构体的大小是不一样哈!
这是怎么一回事呢?我们接下来就来详细讲讲!

位段的内存分配

位段位段,这个位指的是什么?我们之前说过位操作符,位操作符的位指的是二进制位,这里位段的位同样指的是二进制位!
我们这里的_完全是为了和普通的结构体成员做出区别没有特别含义,但是我们:后面的那个数字就决定了我们想要这一个变量占据几个二进制位!
我们再来强调一遍:我们创建变量就会向系统申请空间,那我们第一次将会申请多大的空间呢?这就需要看我们第一个变量的类型了,这里是int类型的,那我们就伸手向系统索要了四个字节的大小,也就是三十二个比特位的大小,
那我们第一个变量占了两个比特位,第二个成员占了五个比特位,这里需不需要考虑什么内存对齐呢?
注意啊,是不需要的!就是因为这点我才会选择加上位段这个知识点!!
位段的计算是不需要考虑什么内存的对齐的,那个是结构体才需要考虑的,我们位段只管放进去就行。
然后第三个成员又占了10个比特位的大小,这前三个成员就一共占了十七个比特位了,目前我们申请的四个字节也就是三十二个比特位就只剩下了十五位,然后第四个成员我们一下子要占用三十个比特位,这下子好了,不够了怎么办呢?不够我们就再向空间申请呗,这次还是申请的一个int的大小。
(这里我们再补充一点:位段的成员必须是int,(unsigned int),(signed int),这三者之一,或者不用int家族,我们就使用char家族,这样子内存不够了我们申请的是几个字节就有了抓手。)
好,申请好了之后我们就可以放数据了。
当然,我们刚才说的这一切都是基于vs2019平台下测试得来的,其实每个编译器对于位段的实现都是大不相同的!

位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
    我们之前讲过的int一般被作为有符号int处理,但是那是在我们的vs2019上面进行的测试,对于其他的平台呢?不到啊!!

  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)
    在64位环境下面我们的int就升级了,它将不再是四个字节,而是翻了个倍,变成了八个字节!那这个操作环境是不是也影响着我们对于位段的把握呢?

  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
    这是不是有点像我们之前讲到的大小端的区别?我们创建了变量申请了空间,那么空间的使用必然需要我们做出猜想,去算计这个空间的使用是从左向右,是大端字节序还是小端字节序,一个道理!

  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
    这就是我们在刚才做出的判断,就是当我们放完了第一个成员,如果还有空间可以放得下下一个成员,那这些空间我们是否还使用呢?
    总结:
    跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

位段的应用

位段的应用,这个比较高级了,就比如我们的ip地址,我们不可能开辟出一段很大的空间只为了存放那么几个数据,所以我们可以合理规划这个IP地址
,前面几个比特位存放着什么,后面的几个比特位又存放着什么,当然了,IP地址到目前为止我都还没有接触到,自然是没有办法给兄弟们解惑啊!!

好的兄弟们,那么今天的内容就到这里了,希望我的这篇博客能对您有些许帮助,我是阿涛,祝你天天开心!!!
百年大道,你我共勉!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值