2014-01-29 64 views
4

當變量與聯合關聯時,編譯器通過考慮最大內存的大小來分配內存。所以,工會的規模等於最大成員的規模。所以它意味着更改任何成員的值將會改變其他成員值。 但是當我執行以下代碼,C中的聯合內的結構C

output: 4 5 7.000000 
union job 
{ 
    int a; 
    struct data 
    { 
     double b; 
     int x 
    }q; 
} w; 


w.q.b=7; 
w.a=4; 
w.q.x=5; 

printf("%d %d %f",w.a,w.q.x,w.q.b); 
return 0; 
} 

問題是,第一I分配的值,並隨後被修飾q.x的值,則a的值將由q.x.重寫但是在輸出中它仍然顯示出a和q.x的原始值。我無法理解爲什麼會發生?

+1

我相信OP的編輯非常清楚地表明問題是什麼(我正在改寫):「當我應該覆蓋結構中的數據時,數據如何變化」。我有一個答案准備好......我不能在問題關閉時發佈。 – Floris

+1

@弗洛里斯現在開放。等待你的答案。 –

+0

@JanDvorak謝謝 - 它現在在那裏。 – Floris

回答

1

你可以看到更改後的值,如果您運行下面的代碼: -

#include <stdio.h> 

union job 
{ 
    struct data 
    { 
     int x; 
     double b;  
    }q; 
    int a; 
} w; 

int main() { 

    w.q.b=7; 
    w.a=4; 
    w.q.x=5; 

    printf("%d %d %f",w.a,w.q.x,w.q.b); 
    return 0; 
} 

OUTPUT:5 7.000000

我稍微修改了工會內部的結構,但是,說明你的關心。

5

你的理解是正確的 - 數字應該改變。我拿走了你的代碼,並添加了一點點,以告訴你到底發生了什麼。

真正的問題是相當有趣的,並且與浮點數在內存中表示的方式有關。

首先,讓我們創建一個映射在你的結構中使用的字節數:

aaaa 
bbbbbbbbxxxx 

正如你所看到的,與a前四個字節0​​重疊。這將變得重要。

現在我們來看看double通常存儲的方式(我從Mac的角度寫這篇文章,內容是64位Intel架構,所以內存中的格式確實是IEEE754格式) :

enter image description here

這裏要注意的重要一點是,Intel的機器是「小尾」 - 也就是說,將首先被存儲的號碼是「在正確的事」,即最低顯著位的「分數」。

現在讓我們來看看一個程序,做同樣的事情,你的代碼一樣 - 但打印出結構的內容,所以我們看到正在發生的事情:

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

void dumpBytes(void *p, int n) { 
    int ii; 
    char hex[9]; 
    for(ii = 0; ii < n; ii++) { 
    sprintf(hex, "%02x", (char)*((char*)p + ii)); 
    printf("%s ", hex + strlen(hex)-2); 
    } 
    printf("\n"); 
} 

int main(void) { 
static union job 
{ 
    int a; 
    struct data 
    { 
     double b; 
     int x; 
    }q; 
} w; 


printf("intial value:\n"); 
dumpBytes(&w, sizeof(w)); 
w.q.b=7; 
printf("setting w.q.b = 7:\n"); 
dumpBytes(&w, sizeof(w)); 
w.a=4; 
printf("setting w.a = 4:\n"); 
dumpBytes(&w, sizeof(w)); 
w.q.x=5; 
printf("setting w.q.x = 5:\n"); 
dumpBytes(&w, sizeof(w)); 

printf("values are now %d %d %.15lf\n",w.a,w.q.x,w.q.b); 
w.q.b=7; 
printf("setting w.q.b = 7:\n"); 
dumpBytes(&w, sizeof(w)); 
printf("values are now %d %d %.15lf\n",w.a,w.q.x,w.q.b); 
return 0; 
} 

和輸出:

intial value: 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

全零(我聲明變量static - 確保一切都會被初始化)。請注意,該函數打印出16個字節,即使您可能認爲其最大元素爲double + int的結構應該只有12個字節長。這與字節對齊有關 - 當最大元素長度爲8個字節時,結構將在8位邊界上對齊。

setting w.q.b = 7: 
00 00 00 00 00 00 1c 40 00 00 00 00 00 00 00 00 

讓我們來看看代表他們的正確順序double字節:

40 1c 00 00 00 00 00 00 

Sign bit = 0 
exponent = 1 0000 0000 0111b (binary representation) 
mantissa = 0 

setting w.a = 4: 
04 00 00 00 00 00 1c 40 00 00 00 00 00 00 00 00 

當我們寫信a,我們修改了第一個字節。這對應於尾數,這是的至少顯著位現在(在十六進制):

00 00 00 00 00 00 04 

現在尾數的格式意味着1到此號碼的左側;因此將0中的最後一位更改爲4,將數字的大小改變了很小的一部分 - 您需要查看十進制的十進制數來查看它。

setting w.q.x = 5: 
04 00 00 00 00 00 1c 40 05 00 00 00 00 00 00 00 

5在自己的小空間書面

values are now 4 5 7.000000000000004 

注 - 當我用了大量的數字,你可以看到,b最低顯著的部分是不完全7 - 即使double是完全能夠精確地表示整數的。

setting w.q.b = 7: 
00 00 00 00 00 00 1c 40 05 00 00 00 00 00 00 00 
values are now 0 5 7.000000000000000 

再次寫入7到雙後,你可以看到,第一個字節是再次00,現在printf語句的結果確實是7.0完全相同。

所以 - 你的理解是正確的。問題在於你的診斷 - 數量不同,但你看不到它。

通常,尋找這些東西的好方法就是將數字存儲在臨時變量中,然後查看其差異。那麼你會很容易找到它。

+0

@JosephQuinsey你是對的,我記得不正確(我想我最近被'scanf'問題困擾了,它讓我更加小心「正確的」格式說明符。參考 - http://stackoverflow.com/a/6395747/ 1967396。我會更新我的答案 – Floris

1

實際上,指令w.a = 4覆蓋了w.q.b的數據。這裏是你的記憶的樣子:

After w.q.b=7; After w.a=4;  After w.q.x=5; 

|0|1|0|0|0|0|0|0| |0|1|0|0|0|0|0|0| |0|1|0|0|0|0|0|0| \  \ 
|0|0|0|1|1|1|0|0| |0|0|1|1|1|0|0|0| |0|0|1|1|1|0|0|0| | w.a | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |  | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|0| |0|0|0|0|0|1|0|0| /  | w.q.b 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|   | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|   | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|   | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|   /

----------------- ----------------- ----------------- 

|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| \ 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| | w.q.x 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| | 
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|1| /

正如你所看到的w.q.b 30位從0改爲1由於4分配給前4個字節,但這種變化是太低了,因爲只有尾數部分受到影響,並且打印w.q.b的精度不顯示此更改。

+1

你有正確的想法,但我認爲你已經把你的排序搞亂了,但也許你正在研究一個大型機器?查看我的答案和它生成的輸出。編寫'a'的變化與指數比特非常接近 - 因此接近尾數的最高有效位。不得不說我喜歡「比特藝術」! – Floris

+0

關於「字節順序」的說明。字節按順序存儲在例如,如果你把數字'4'作爲'int',那麼存儲它的四個字節是'00 00 00 04',在一個「小端」機器中,它們實際上被存儲爲'04 00 00 00「(」最小的字節第一「)。相同的雙打等你的圖支持ses事物以「自然」(大端)順序存儲;這在某些平臺(68000系列,PPC)上是正確的,但在[大多數] Intel(x86)機器上不是這樣。在這種情況下,差異很重要 - 它會改變'double'中哪些位被'int'覆蓋。 – Floris

+0

@弗里斯坦克澄清! – rullof