2017-05-24 132 views
3

我一直在考慮在C-過程如下分配:位域和聯盟 - 意想不到的結果用C

enter image description here

我實現分配給解碼8字節長的長整型131809282883593如下:

#include <stdio.h> 
    #include <string.h> 

    struct Message { 
     unsigned int hour : 5; 
     unsigned int minutes : 6; 
     unsigned int seconds : 6; 
     unsigned int day : 5; 
     unsigned int month : 4; 
     unsigned int year : 12; 
     unsigned long long int code : 26; 
    }; // 64 bit in total 

    union Msgdecode { 
     long long int datablob; 
     struct Message elems; 
    }; 

    int main(void) { 

     long long int datablob = 131809282883593; 
     union Msgdecode m; 

     m.datablob = datablob; 

     printf("%d:%d:%d %d.%d.%d code:%lu\n", m.elems.hour, m.elems.minutes, 
     m.elems.seconds, m.elems.day, m.elems.month, m.elems.year,(long unsigned int) m.elems.code); 

     union Msgdecode m2; 
     m2.elems.hour = 9; 
     m2.elems.minutes = 0; 
     m2.elems.seconds = 0; 
     m2.elems.day = 30; 
     m2.elems.month = 5; 
     m2.elems.year = 2017; 
     m2.elems.code = 4195376; 

     printf("m2.datablob: should: 131809282883593 is: %lld\n", m2.datablob); //WHY does m2.datablob != m.datablob?! 
     printf("m.datablob: should: 131809282883593 is: %lld\n", m.datablob); 

     printf("%d:%d:%d %d.%d.%d code:%lu\n", m2.elems.hour, m2.elems.minutes, 
      m2.elems.seconds, m2.elems.day, m2.elems.month, m2.elems.year, (long unsigned int) m2.elems.code); 

    } 

TRY IT ONLINE.

..what給了我一個硬時間是輸出。目前解碼/編碼很好地工作。 9:0:0二零一七年五月三十零日和代碼4195376,預計,但在「datablob」的區別真的不是 - 我想不通爲什麼/它源於:

9:0:0 30.5.2017 code:4195376 
m2.datablob: should: 131809282883593 is: 131810088189961 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 

正如你所看到的datablob是關閉到原來的 - 但不是原來的。我已經諮詢了一位C語言流利的同事 - 但我們無法弄清楚這種行爲的原因。

問:爲什麼斑點互相不同?

獎金-Q:當操作工會Msgdecode包含另一個領域,奇怪的事情發生了:

union Msgdecode { 
    long long int datablob; 
    struct Message elems; 
    char bytes[8]; // added this 
}; 

結果:

9:0:0 30.5.2017 code:0 
m2.datablob: should: 131809282883593 is: 8662973939721 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 

PS:閱讀SO關於位域+聯盟問題給我的印象是他們相當不可靠。這可以說是普遍的嗎?

+2

如果這是你的任務,這是一個可怕的。位場結構對其佈局幾乎沒有任何保證。使用輪班和掩蔽!並且不能保證你可以使用'int','unsigned int'和'_Bool' bitfleids之外的其他值。作爲旁註:您使用的任何類型都不會保證spicif表示或寬度!如果您需要固定寬度,請使用固定寬度類型! – Olaf

+0

@Olaf這是我的任務。所以我最後的PS問題可以用'是'來回答。 :) – Gewure

+0

是的,但我們不是一個輔導服務。通過該導師可以獲得一本好的C書,並根據書中的信息過濾課程材料。作爲一個旁註:讓我分開,我從來沒有給我的學生這樣的任務:與代碼:你會在我的過程中失敗。一些原因:見上文。 – Olaf

回答

3

位域的佈局之間可能存在的struct和任何填充內他們是實現定義的。

C standard的節6.7.2.1:

實現可以分配任何可尋址的存儲單元大到足以容納一個比特字段。如果剩餘足夠的空間,緊接在結構中的另一個比特字段之後的比特字段 應該被打包到相同單元的相鄰比特中 。如果剩餘空間不足 ,則將不匹配的位字段放入 ,則下一單元或與相鄰單元重疊的位是 實現定義的。 一個單元(從高位到低位或從低位到高位)的位域分配順序是實現定義的 。未指定可尋址存儲器的對齊方式。

這意味着您不能依賴符合標準的佈局。

這就是說,讓我們看看如何在這個特殊情況下佈置位。要重申,從這裏開始的一切都是在實現定義的行爲的領域。我們將從第二種情況開始,其中m2.datablob是8662973939721,因爲這更容易解釋。

首先讓我們看一下值的位表示分配給m2

- hour:  9: 0 1001 (0x09) 
- minutes: 0: 00 0000 (0x00) 
- seconds: 0: 00 0000 (0x00) 
- day:  30: 11 1110 (0x3E) 
- month:  5: 0101 (0x05) 
- year: 2017: 0111 1110 0001 (0x7e1) 
- code: 4195376: 00 0100 0000 0000 0100 0011 0000 (0x0400430) 

現在讓我們來看看BLOB值,第一m其分配給blobvalue然後m2這與分配給每個字段逐個上述值:

131809282883593 0x77E13D7C0009      0111 0111 1110 0001 
            0011 1101 0111 1100 0000 0000 0000 1001 

    8662973939721 0x07E1017C0009      0000 0111 1110 0001 
            0000 0001 0111 1100 0000 0000 0000 1001 

如果我們通過觀察從右邊的值開始走左邊,我們可以看到,值9,所以有我們的前5位。接下來是兩組6個零位,用於接下來的兩個字段。之後,我們看到位模式爲30,然後是5.
稍微進一步,我們看到值2017的位模式,但在此值和先前值之間有6位設置爲零。所以它看起來像佈局如下:

  year  ??? month day sec  min hour 
     ------------ ----- --- ---- ------ ----- ----- 
    |   | |  || || ||  ||  |  | 
0000 0111 1110 0001 0000 0001 0111 1100 0000 0000 0000 1001 

所以這是在yearmonth領域之間的一些填充。比較mm2表示,差異在monthyear之間的填充的6位中以及在year的左邊的4位。

我們在這裏沒有看到的是code字段的位。那麼這個結構有多大?

如果再加上這代碼:

printf("size = %zu\n", sizeof(struct Message)); 

我們得到:

size = 16 

這比我們想象的更大可觀。所以讓我們製作bytes array unsigned char [16]並輸出它。的代碼:

int i; 
printf("m: "); 
for (i=0; i<16; i++) { 
    printf(" %02x", m.bytes[i]); 
} 
printf("\n"); 
printf("m2:"); 
for (i=0; i<16; i++) { 
    printf(" %02x", m2.bytes[i]); 
} 
printf("\n"); 

輸出:

m: 09 00 7c 3d e1 77 00 00 00 00 00 00 00 00 00 00 
m2: 09 00 7c 01 e1 07 00 00 30 04 40 00 00 00 00 00 

現在我們看到對應於碼字段中爲平方米表示的0x0400430位模式。在該字段之前還有20個填充位。還要注意,這些字節與該值相反的順序告訴我們我們在一個小端機器上。鑑於值的佈局方式,每個字節中的位也可能是小端。

那麼爲什麼填充?這很可能與對齊有關。前5個字段是8位或更少,這意味着它們每個都適合一個字節。單字節沒有對齊要求,所以它們被打包。下一個字段是12位,這意味着它需要適合16位(2字節)字段。所以6位填充被添加,所以這個字段以2字節的偏移量開始。下一個字段是26位,它需要一個32位字段。這意味着它需要以4字節偏移量開始並使用4個字節,但是由於該字段被聲明爲unsigned long long,在這種情況下其爲8個字節,所以該字段佔用8個字節。如果您聲明此字段爲unsigned int,它可能仍會以相同的偏移量開始,但僅使用4個字節而不是8個。

現在第一種情況下blob值爲131810088189961的情況如何?讓我們來看看它的表現相比,「預期」之一:

131809282883593 0x77E13D7C0009      0111 0111 1110 0001 
            0011 1101 0111 1100 0000 0000 0000 1001 

131810088189961 0x77E16D7C0009      0111 0111 1110 0001 
            0110 1101 0111 1100 0000 0000 0000 1001 

這兩種表示在存儲的數據位相同的值。它們之間的區別在於monthyear字段之間的6個填充位。至於爲什麼這種表示方式不同,當編譯器意識到某些位不可讀或不可讀時,編譯器可能會做出一些優化。通過向聯合添加一個char數組,可能會因爲可能會讀取或寫入這些數據位而導致無法再進行優化。

使用gcc,你可以嘗試在結構上使用__attribute((packed))。這樣做提供了以下的輸出(打印時調整bytes陣列8與循環限制沿後):

size = 8 
9:0:0 30.5.2127 code:479 
m2.datablob: should: 131809282883593 is: 1153216309106573321 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 
m: 09 00 7c 3d e1 77 00 00 
m2: 09 00 7c 85 1f 0c 01 10 

並將以位表示:

1153216309106573321 0x10010C1F857C0009 0001 0000 0000 0001 0000 1100 0001 1111 
             1000 0101 0111 1100 0000 0000 0000 1001 

    131810088189961 0x77E16D7C0009  0000 0000 0000 0000 0111 0111 1110 0001 
             0110 1101 0111 1100 0000 0000 0000 1001 

但即使有此,you could run into issues

所以總而言之,位域不能保證佈局。您最好使用位移和掩碼來獲取位域內外的值,而不是試圖覆蓋它。

+0

謝謝你這個非常詳細的答案,@dbush我非常讚賞它。我試圖在另一臺機器上覆制它,並得到稍微不同的結果,這非常有啓發性。 現在我知道爲什麼不使用位域 - 和'無符號'如果可能。 – Gewure

1

這裏的問題在於,37行,你做的事:

m2.elems.code = 4195376; 

您分配了一個無效的類型,以你的位字段:

struct Message { 
    unsigned int hour : 5; 
    unsigned int minutes : 6; 
    unsigned int seconds : 6; 
    unsigned int day : 5; 
    unsigned int month : 4; 
    unsigned int year : 12; 
    unsigned long long int code : 26; <-- invalid 
}; 

見:https://www.tutorialspoint.com/cprogramming/c_bit_fields.htm 在主題:位域聲明

那裏說你只能用int,signed int and unsigned int as Type。

我認爲編譯器將m2.elems.code解釋爲int,我不知道他的確如何處理大於max int的賦值。

1

重申,位字段結構中位的佈局不能保證(即它依賴於編譯器),所以這種位操作不是好的做法。爲了實現這種功能,應該使用位操作。

的這一個簡單的例子可能是:

#define HOUR_BIT_START 59 // The bit number where hour bits starts 
#define HOUR_BIT_MASK 0x1F // Mask of 5 bits for the hour bits 

unsigned int getHour(long long int blob) 
{ 
    return (unsigned int)((blob >> HOUR_BIT_START) & HOUR_BIT_MASK); 
} 

int main (int argc, char *argv[]) 
{ 
    unsigned long long int datablob = 131809282883593; 

    printf("%d:%d:%d %d.%d.%d code:%lu\n", getHour(datablob), getMinutes(datablob), getSeconds(datablob), getDay(datablob), getMonth(datablob), getyear(datablob), getCode(datablob)); 
} 

我會留下的其他get*()功能的實現你