結構體位域在內存中的分佈與平臺大小端的關係

本博客與本人在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」的推論。

相關文章
相關標籤/搜索