为什么使用"指定初始化程序"来初始化C union?给出随机值?

时间:2016-12-03 15:15:04

标签: c initialization c99 unions buffer-overflow

我有一个" bug"我花了很长时间追逐:

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上。

1 个答案:

答案 0 :(得分:2)

在C11中,§6.7.9表示

  

初始化应在初始化器列表顺序中进行,每个初始化器为特定子对象提供,覆盖同一子对象的任何先前列出的初始化器;未明确初始化的所有子对象应与具有静态存储持续时间的对象隐式初始化。

但是隐藏的填充位不是子对象,它不会受到这种约束,因为从匿名style的角度来看它不存在,所以编译器没有初始化不是成员的东西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]的两个成员之间有3个字节的填充,它们不属于任何子对象,初始化它们会浪费时间,因为在正常情况下您无法访问它们。