结构体、位段、共用体、枚举相关内存知识(c语言)

目录

一、结构体相关内存分配

二、位段

三、共用体、枚举


一、结构体相关内存分配

首先,先上一段代码:

struct Example
{
    char a;  
    char b;
    double c;
    int d;
};

int main()
{
    printf("%d\n", sizeof(struct Example));
    return 0;

可能初学结构体的时候,你会认为这段结构体的内存占用不就是两个char类型的变量和一个double类型int类型的变量内存之和嘛:1+1+8+4=14。可是,当你在vs2022(我所用的版本)下编译的时候会发现不是这么简单的:

24?和14差异如此巨大的数?不禁心生疑惑:为什么会这样呢?这就源于结构体的内存对齐机制了。

以下就是我理解的储存机制:

1.首先在内存区找到一个起始位置,设该位置偏移量0,以此往下走即地址增加就偏移量加一。

2.然后第一个变量就从偏移量为0地址开始储存,一个偏移量就是一字节。

3.数据储存的字节数就是其数据类型的所占字节数,但是,起始位置取决于对齐数的整数倍

4.对齐数就是当前数据类型所需要的内存和默认对齐数进行比较(默认对齐数与当前编译环境有关,vs2022里面默认是8),取出里面的较小的那个数。

5.最后,如果排完最后一个变量的内存后,和所有变量中的最大对齐数和默认对齐数较小的那个的整数倍一致就是,否则就是要比现在要大的整数倍作为其内存。

根据以上的机制,我们就来画一画我们上面的那个程序里面的内存如何实现存储分配的:

 黑色表示char类型的,一开始变量a储存在0偏移量处,存一个字节,第二个变量b也是char类型的,其需要一字节内存,和默认对齐数比较(8 > 1)1小,所以char b的对齐数就是1,所以其的储存位置就应该是1的整数倍,所以下面的1位置就可以存储了。然后要储存double c,我们知道一个double类型需要8字节,和默认相同,所以取8为其对齐数,起始位置就要找8的整数倍,因为上面储存了第二个变量之后就是2了,不是8的整数倍,所以就要向下找,知道找到8,然后就从偏移量8开始存,而1-8之间的内存全部浪费掉了,依次,当储存完最后一个int类型的变量之后,发现一共存储了20个字节,但是在这个结构体的所有变量中,最大的对齐数就是8,二20并不是8的整数倍,所以就要取比这个大的整数倍3*8=24即可。

综上,我们了解了结构体在内存里的对齐储存机制。但是我们发现,它这样储存的话会浪费空间啊,那为什么要这样储存呢?

这样做总是有理由的,你会发现这样做效率会更高一点,这就是所谓的牺牲空间来节省时间吧。

二、位段

上面介绍了结构体里面的内存实现细节,那么,既然浪费空间,就设计了一个节省空间的。记住,只是为了节省空间,不具有移植性与跨平台性,为什么呢?我们先来说说如何实现的。

先来一串代码:

//位段:
struct Examle
{
    int _a : 2;//32
    int _b : 5;
    int _c : 10;
    int _d : 30;//32
};

int main()
{
    printf("%d\n", sizeof(struct Examle));
    return 0;
}
 

这里也是套用的struct结构体的形式,但是这里算出来的结果是:8

 为什么呢

就是给的是int类型的(这里类型必须要求是:int,unsigned,int/signed int、char),就给32个bit内存也就是:4字节。如果是char类型就是分配8bit。分配之后没用完继续用完,但是必须是刚好在分配的这个里面,如果不足,就必须重新开辟。

上面程序过程就是32分配给a,b,c之后如果想包含d的话但是32显然不够,所以就新开辟了一个32bit的空间。

而这里的无法移植和跨平台性就是放和取内存不知道从哪里取,方式未知,都是混杂的。

三、共用体、枚举

有时候为了需求,比如一个人有着不同的身份的时候,等等,下面有一个代码:

union Un
{
    short s[7];
    int n;
};

int main()
{
    printf("%d\n", sizeof(union Un));
    return 0;
}

这里会是多少呢?

答案是 16,你以为是14吧?因为按理说在共用体里面就是共用一个内存,在需要用哪个的时候就调用哪个,这里明显s数组更大,7*2=14,但是这里也有对齐原则,是最大对齐数的整数倍,4的整数倍,所以还是16哦。

 那么为什么说公用内存呢?

union Un
{
    short s[7];
    int n;
}c1;

int main()
{
    printf("%d\n", sizeof(union Un));
    printf("%x\n", &(c1.s));
    printf("%x\n", &(c1.n));
    return 0;
}

这段代码便就是打印其地址,打印出来的结果就是:

 所以就是公用。

我们知道我们数据在内存里面的存储是有字节序的,一般是小段字节序,及就是地位在低地址,高位在高地址。那么我们能否利用共用体这个优势来封装一个判断字节序的函数呢?

int zijiexu()
{
    union un
    {
        char i;
        int n;
    }u;
    u.n = 1;
    return u.i;
}

int main()
{
    if (zijiexu() == 1)
    {
        printf("小端\n");
    }
    else {
        printf("大端\n");
    }
}

 

 只能说妙啊妙啊。

而枚举的话就是:

enum Sex
{
    枚举的可能取值;
}

enum Sex s = 取值;
创建变量,和结构体类似。

枚举的可能取值都是赋予了值的。
按照顺序,从上到下默认取值 0,1,2,3
如果一开始在一个值赋予,那么从这个值往下默认就是从这个基础上往下加即可。

枚举里面的取值,为枚举常量,初始化可,出了枚举就不能对其进行修改。
和#define的功能类似,但是枚举具有检查,美观,直观。
便于调试。(看到的和实际的一样的)
 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值