为什么使用 "designated initializer" 初始化 C 联合会给出随机值?
Why is initializing C union using "designated initializer" giving random values?
我有一个 "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.5
和 tcc
,但不会发生在 gcc 4.9.2
。
在 C11 中,§6.7.9 指出
The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
但是隐藏的填充位不是子对象,它不受约束,因为从匿名 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 个字节的填充,它们不属于任何子对象,初始化它们会浪费时间,因为在正常情况下您无法访问它们。
我有一个 "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.5
和 tcc
,但不会发生在 gcc 4.9.2
。
在 C11 中,§6.7.9 指出
The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
但是隐藏的填充位不是子对象,它不受约束,因为从匿名 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 个字节的填充,它们不属于任何子对象,初始化它们会浪费时间,因为在正常情况下您无法访问它们。