2016-12-03 175 views
1

我有一個「錯誤」,我花了相當長的一段追逐:爲什麼使用「指定初始值設定項」初始化C union給出隨機值?

typedef union { 
    struct { 
     uint8_t mode: 1; 
     uint8_t texture: 4; 
     uint8_t blend_mode: 2; 
    }; 
    uint8_t key;  
} RenderKey; 

後來這個聯盟將被初始化(堆棧):

Buffers buffers[128]; // initialized somewhere else 

void Foo(int a, int b) 
{ 
    //C99 style initialization (all the other values should be 0) 
    RenderKey rkey = {.blend_mode = 1};  

    //rkey.key would sometimes be >= 128 thus would write out of array bounds 
    DoStuffWithBuffer(&buffers[rkey.key]); 
} 

這似乎表明的最後一位聯合位域不會被初始化。所以,我將未使用的位固定它:

typedef union { 
    struct { 
     uint8_t mode: 1; 
     uint8_t texture: 4; 
     uint8_t blend_mode: 2; 
     uint8_t unused: 1; 
    }; 
    uint8_t key;  
} RenderKey; 

這工作,但我不明白爲什麼準確。 這個隨機的1位來自堆棧上的隨機垃圾,但爲什麼C99樣式初始化不在這裏工作?由於union和匿名struct

這發生在Clang 3.5tcc上,但不在gcc 4.9.2上。

回答

2

在C11它在§6.7.9的是

初始化應初始化列表順序發生陳述,提供給特定子對象重寫爲同一子對象任何前面列出的初始化每個初始化; 未明確初始化的所有子對象應隱式地初始化爲與具有靜態存儲持續時間的對象相同。

但隱藏的填充位不是一個子對象,它不接受該約束,因爲從匿名struct來看它不存在,所以編譯器不會初始化的東西是不是成員struct,畢竟這並不奇怪。

一個類似的例子是有一些像

#include <stdio.h> 

typedef struct { 
    unsigned char foo; 
    float value; 
} Test; 

int main(void) { 
    Test test = { .foo = 'a', .value = 1.2f}; 

    printf("We expect 8 bytes: %zu\n", sizeof(Test)); 
    printf("We expect 0: %zu\n", (void*)&test.foo - (void*)&test); 
    printf("We expect 4: %zu\n", (void*)&test.value - (void*)&test); 

    unsigned char* test_ptr = (unsigned char*) &test; 

    printf("value of 3rd byte: %d\n", test_ptr[2]); 
} 

什麼期望test_ptr[2]是?在struct的兩個成員之間有3個字節的填充不是任何子對象的組成部分,因此初始化它們將浪費時間,因爲在正常情況下無法訪問它們。

+1

我可以遵循匿名的'struct'只有7位,並且只對這些位進行初始化。這種情況令人困惑的是變量'key'在那裏,它是一個聯合。 – JBeurer

+1

@JBeurer:你沒有通過'key'來初始化union,而是通過'struct'來完成它。所以有一個「聯盟」的事實是無關緊要的。當你選擇通過結構來初始化你的整個對象時,你將「限制」RenderKey作爲'struct'。 – Jack

+0

換句話說,當你初始化'union'時,你選擇你要初始化它的變量。如果它是'union {uint8_t a; uint32_t b}'並且它使用'a'進行初始化(並且'b'的24個最重要的比特將最終變爲隨機的),這將不太令人困惑。 – JBeurer

相關問題