c语言之字节对齐-更多细节和位段

1.更多自定义类型字节对齐
(1)struct作为数据成员

struct Test                // 8+16+4+4
{
    int a;                // 4+4
    struct                // 8+2+2+4
    {
        double b;        // 8
        char c;            // 1+1
        short d;        // 2
    };
    int e;                // 4
};
之前介绍了一般的自定义数据类型的字节对齐,当自定义类型作为struct的类型成员时,情况和普通类型数据有一些差异。
首先,a大小4bytes,然后struct中的b大小8bytes,c大小2bytes,d大小2bytes。b对齐8bytes,c向下对齐1+1=2bytes,d对齐2bytes。
整个struct大小为8+2+2=12,将结构体单独看作一个成员,自身内部的最大对齐值,自定义类型按最大对齐值+4(16)。a向struct对齐。
struct的有效对齐值是8,所以a就是4+4=8。e的大小是4,整个Test对齐值8+16+4+4=32。
(2)使用#pragma pack(value)对程序指定对齐值
#pragma pack(1)                    // 19
struct Test2                // 4
{
    int a;                // 4+4
    struct                // 7
    {
        short b;        // 2+6
        double c;        // 8
        char d;            // 1
    };
    int e;                // 4
};
按照Test来看Test2,a,b,c,d,e的对齐值分别为8,8,8,1,4,结构体本身对齐,先算结构体struct对齐值7,Test2的自身对齐值4,所以29+7+4=40bytes。
这时候我们使用#pragma pack(value),value就是我们的指定对齐值。#pragma pack(1)相当于不对齐(强制要求内存)。
这时候的Test2大小等于4+2+8+1+4=19bytes。节省了内存空间,但是降低了CPU运行效率。
(3)自定义类型中存在数组成员的对齐值
struct Test3                // 4
{
    int a;                // 4+4
    struct                // 4                
    {
        double b[10];            // 80
        char c;            // 1+1
        short d;        // 2
    };
    int e;                // 4
};
首先看内部结构体,double b[10]大小80bytes,c对齐2bytes,d对齐2bytes,内部struct自身对齐加4(按照b而不是数组),内部struct对齐值就是80+2+2+4=88。
然后a的对齐值4+4,e的对齐值4,Test3自身对齐+4Bytes,就是104。
(4)Struct内部嵌套(结构体有名字)的字节对齐
struct Test4                        //8
{
    int a;
    struct t                    // 有名字时候升级结构体为一个类型,不占空间
    {
        double b;
        char c;
        short d;
    };
    int e;
};
按照刚才对于内嵌struct的解释,这个时候Test4的各成员对其字节应该是8+8+2+2+4+4+4=32Bytes,但是实际sizeof(Test4)的大小是8。
这就和之前的不同了,因为内嵌的struct有名字t,这代表了变量b,c,d是结构体t的成员变量,struct t的数据在另一片内存空间。在Test4中struct t
只做一个声明,类似于函数和结构体的声明。故Test4的字节大小为4+4=8。
(5)Struct内部嵌套(结构体有名字且声明了变量)的字节对齐
struct Test5                        //32
{
    int a;                        //4+4
    struct tt                
    {
        double b;                //8
        char c;                    //1+1
        short d;                //2+4
    }ttt;
    int e;                        //4+4
};
在这种情况下struct tt声明了一个结构体的变量ttt。虽然struct tt依然只是一个结构体的声明,ttt就是struct Test5的成员数据了。注意和(3)(4)
的区别,这种情况下就和(3)的一样,字节大小为4+4+8+2+2+4+4+4=32。

2.位段(位域)
(1)位段,也被叫做位域。他是c\c++中一种特殊的数据类型(的表现)。一般的数据类型最小的内存单位即是一个字节byte,但是c从语言的层面上允许
程序员将内存byte拆分成位(bit)的形式,但是必须存放在struct(自定义类型)中。这种存放的数据形式我们叫做位段。在网络传输或者内存尤为珍贵的
嵌入式程序中,我们就可以使用位段来定义数据。
(2)位段的表现形式:
struct Test1            // 空间至少按照1字节来开辟
{
    char a : 2;        
    char b : 1;
    char c : 3;            
};
我们定义了a大小为2bits,b的大小1bit,c的大小3bits。所以2+1+3=6bits,但是内存的最小分配单元就是1byte。所以Test1的大小空间就是1byte。
相当于把一个字节中的二进制划分成了三个不同区域。
(3)位段不能跨字节存储
struct Test2            // 2 
{
    char a:3;
    char b:4;
    char c:2;        
};
Test2的三个数据大小为3+4+2=9bits,已经超过1byte了,这个时候就需要在新的字节中占两个位空间,而且会留一个bit的空间,c存储到第二个字节中。
即Test2占2bytes,a存储在第一个字节单元中,c存储在第二个字节单元中。
(4)跨类型的位段和字节对齐
struct Test3        // 8
{
    // char a : 10;            
    char b : 1;        // 1+3
    int c : 1;        // 4
    // int d : 32;        // 最多b只能32个bit
};
位段也可以不是char类型的。比如char a:10就是错误的,因为char a 最多只能占8个bits。因此int d:32就可以有32bits。
位域不能跨类型存储,Test3中c不能和b共享一个byte的空间。c在b之外额外开辟四个(int)的内存大小空间。
位段也必须遵照字节对齐。所以b的字节大小向c对齐就是1+3,c的大小4bytes,struct Test3的大小空间也就是1+3+4=8bytes。

3.总结
(1)关于结构体声明

struct Test;
printf("%d\n",sizeof(Test));        1
对于一个结构体Test,我们只对他作了声明,结构体本身也是要占1个byte的空间。因为内存要寻址(相当于strcut Test的入口地址)。
(2)关于位段
struct Test1            
{
    char a : 2;        
    char b : 1;
    char c : 3;            
};
Test1 t;
    t.a=1;
    t.b=0;
    t.c=2;
我们知道一个int的取值范围为-2^31~2^31-1,char的取值范围为-128~127。由于补码的存在,所以带符号的位段也有一个取值范围。
如果对位段的赋值超过了其取值范围,就会发生意料之外的错误(数据错乱)。所以对于以上3个变量。
a的取值范围为-2~1,b的取值范围为{-1,0},c的取值范围为-4~3。
(3)关于有符号的数据类型取值范围的补充
signed char a = (unsigned char)-1;                 // 0xFFFFFFFF-> 0xFF
unsigned char b = a;                                      // 0xFF
int c=a;                                                          // 0xFF-> 0x000000FF
int d=b;                            

printf("%d %d %d %d\n",a,b,c,d);                // -1 255 255 255
高字节数据和低字节数据进行转换时,很多情况就会有高位截取和高位补0的情况
(4)总结
要认清在结构体嵌套的情况下,它所代表的意义。嵌套结构体时的字节对齐不同,是因为嵌套时表达的不同的含义。
位段中则要注意内存的最小开辟单元是byte,所以编译器只是模拟将内存从byte划分成8个bits。在对存储空间要求严格的情况下可以使用位段。
除此之外,也可以将一个数据通过字节转换(字节码)来存储,也能节省空间。(试着用2bytes来存储一个日期格式2019-12-12)。
位段是把一个字节划分成多个单元,但是位段却不能跨字节存储。超出的必须存放在下一个字节中。不同类型的位段也不能共用一片字节空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值