结构体位域在内存中的分布与平台大小端的关系

本博客与本人在csdn上的博客同步:https://blog.csdn.net/qiuzhizhecsd/article/details/75144275  转载请注明出处markdown

1.    测试用例

1.1.    测试用例1

struct
{
    UINT8 a:1;
    UINT8 b:3;
    UINT8 c:4;
} A;
main()
{
int i;
    UINT8 *p;
    A.a=1;
    A.b=1;
    A.c=1;
    for(i=0;i<sizeof(A);i++)
    {
        p=((UINT8 *)&A)+i;
        printf("0x%02x ",*p);
    }
    printf("\n");
}
大端输出结果:0x91
小端输出结果:0x13

1.2.    测试用例2

struct
{
    UINT16 a:4;
    UINT16 b:4;
    UINT16 c:4;
    UINT16 d:4;
} B;
void main()
{
    int i;
    UINT8 *p;
    B.a=1;
    B.b=3;
    B.c=7;
    B.d=15;
    for(i=0;i<sizeof(B);i++)
    {
        p=((UINT8 *)&B)+i;
        printf("0x%02x ",*p);
    }
    printf("\n");
}
大端输出结果:0x13 0x7f
小端输出结果:0x31 0xf7


1.3.    测试用例3

struct
{
    UINT8 a:4;
    UINT8 b:4;
} C;
void main()
{
    int i;
    UINT8 *p;
C.a=15;
    C.b=0;
    for(i=0;i<sizeof(C);i++)
    {
        p=((UINT8 *)&C)+i;
        printf("0x%02x ",*p);
    }
    printf("\n");
}
大端输出结果:0xf0
小端输出结果:0x0f




1.4.    测试用例4

struct
{
    UINT16 a:3;
    UINT16 b:8;
    UINT16 c:5;
} D;
void main()
{
    int i;
    UINT8 *p;
    D.a=1;
    D.b=3;
    D.c=7;
    for(i=0;i<sizeof(D);i++)
    {
        p=((UINT8 *)&D)+i;
        printf("0x%02x ",*p);
    }
    printf("\n");
}
大端输出结果:0x20 0x67
小端输出结果:0x19 0x38

2.    我对位域与大小端关系的理解

2.1.    大小端的定义

所谓大端,是指一个多字节变量的低权重字节存放在低地址。
所谓小端,是指一个多字节变量的高权重字节存放在低地址。
如0Xabcd ,大端模式ab存放在低地址,小端模式cd存放在地地址。

2.2.    位域的排布与大小端的关系

1)有一个变量:UINT16 t;
2)执行如下语句:t=0x1234;
此时不管在大端机器仍是小端机器上,变量t的打印结果都是0x1234,0x1234写成二进制形式就是0001 0010 0011 0100
3)如今定义一个位域结构体struct myst:
struct myst
{
    UINT 16 a:3;
    UINT16 b:8;
    UINT16 c:5;
} ;
4)用struct myst位域结构体解释t变量:
struct myst *p;
p=((struct myst *)&t);

5)咱们的问题来了:
p->a,p->b,p->c到底是t变量“0001 0010 0011 0100”中的哪一步分呢?
6)正确答案以下图所示:
 
上图所描述的就是t变量的内容0x1234的二进制值,此时请不要考虑大小端内容在内存中的排布状况,这只是一个写在纸上的二进制数字。
数字上方的线段为大端模式,数字下方的线段是小端模式。
红色线段表示struct myst 中的UINT 16 a:3;
绿色线段表示struct myst 中的UINT 16 a:8;
蓝色线段表示struct myst 中的UINT 16 a:5;
总之,大端模式从左向右排列位域,小端模式从右向左排列位域。
也就是说, 大端模式位域从高权重bit向低权重bit排列,小端模式反之
7)打印结果:
Printf(“0x%02x\n”,p->b);
由于绿色区域为10010001,大端模式打印:0x91   
由于绿色区域为01000110,小端模式打印:0x46   


2.3.    关于规则背后缘由的思考

2.3.1.    编译器的设计者想知足两个条件

   条件一:不管大端小端,位域结构体的成员先排布在低地址字节,再排布在高地址字节。好比struct myst 中的UINT16 a:3就必须排布在两个字节中地址低的字节。测试

   条件二:将内存中的数据按照权重顺序用二进制写在纸上,单个位域在纸上的区域不能被分割。因此只有从右向左和从左向右排列两种选择spa

2.3.2.    假设大端模式从右向左分配位域

 
上图中,第二排数字假设大端模式依然是从右向左分配位域。能够看出,第二排数字是按照从右向左分配位域的,a:3被分配在了最右侧。
然而因为大端低权重字节存高地址,在内存中,a:3却被分配到了高地址字节。这与以前2.3.1介绍的必须知足的“条件一”矛盾。所以不能从右向左分配。

2.3.3.    假设大端模式从左向右分配位域

 
上图中,假设大端模式从左向右分配位域,第二排数字中的红色线段a:3被分配在了第二排最左边。而且内存中a:3被分配到了低地址字节,这与2.3.1中必须知足的条件相符。

 

2.4.    关于背后的缘由的思考2     

      偶然看到当年写的文章,感受当年的描述虽然能够自圆其说,可是没有切中实质。如今从新思考后补充一下。.net

      个人结论是,编译器的设计者要知足两个条件:设计

      条件一:不管大端小端,位域结构体的位域成员先排布在低地址字节,再排布在高地址字节。好比struct myst 中的UINT16 a:3就必须排布在两个字节中地址低的字节。(同2.3.1的条件2)blog

      条件二:使用移位指令来读取结构体中的某一段位域。(2.3.1中的条件2的背后原理)内存

      cpu若是要读取位域,就须要使用移位指令,很明显,移位指令的运行原理不会仅仅是简单的将内存的某一段区域移出来,由于还须要考虑大小端。编译器

      好比一个16位变量是0xff80同步

      在小端模式下内存中的位排布是:(高地址)11111111 10000000(低地址)博客

      在大端模式下内存中的位排布是:(高地址)10000000 11111111(低地址)
      若是我要把这个变量的高9位移位出来,小端模式直接把左边的9位移出来,大端模式就更复杂了,由于数据不连续,须要把左1位和右8位移出来,而后拼在一块儿。总之,大端机器的跨字节移位是不连续的,小端机器的跨字节移位是连续的。

      结构体的位域也是经过移位来读取数据的,由于大端机器和小端机器的移位方法不一样,因此相同的内存数据在大端机器和小端机器下的结构体位域值也不一样。大端机器之因此从高权bit向低权bit排布位域,就是为了使用移位命令将一个跨字节的位域读取出来。2.3.1中的“条件2”也是反应了这一点。或者说“2.3.1的条件2”是“2.4的条件2”的推论。

相关文章
相关标签/搜索