【发布时间】:2016-11-21 23:24:29
【问题描述】:
假设我有这样的工会
union buffer {
struct { T* data; int count; int capacity; };
struct { void* data; int count; int capacity; } __type_erased;
};
如果我在 C11 别名规则下混合读取/写入匿名结构成员和 __type_erased 成员,我会遇到麻烦吗?
更具体地说,我对独立访问组件(例如通过不同的指针)时发生的行为感兴趣。举例说明:
grow_buffer(&buffer.__type_erased);
buffer.data[buffer.count] = ...
我已经阅读了我能找到的所有相关问题,但我仍然不是 100% 清楚这一点,因为有些人似乎认为这种行为是未定义的,而另一些人则认为这是合法的。此外,我发现的信息是 C++、C99、C11 等规则的混合体,很难消化。在这里,我对 C11 强制执行并由流行编译器(Clang、GCC)展示的行为非常感兴趣
编辑:更多信息
我现在已经对多个编译器进行了一些实验,并决定分享我的发现,以防有人遇到类似问题。我的问题的背景是我试图用纯 C 编写一个用户友好的高性能通用动态数组实现。这个想法是数组操作是使用宏和重型操作(如增长数组)进行的使用别名类型擦除的模板结构执行。例如,我可以有这样的宏:
#define ALLOC_ONE(A)\
(_array_ensure_size(&A.__type_erased, A.count+1), A.count++)
必要时增加数组并返回新分配项的索引。规范(6.5.2.3)规定允许通过不同的联合成员访问同一位置。我对此的解释是,虽然 _array_ensure_size() 不知道联合类型,但编译器应该知道成员 __type_erased 可能会因副作用而发生变异。也就是说,我认为这应该可行。然而,这似乎是一个灰色地带(老实说,规范并不清楚什么构成了成员访问)。苹果最新的 Clang (clang-800.0.33.1) 没有问题。代码在没有警告的情况下编译并按预期运行。但是,当使用 GCC 5.3.0 编译时,代码会因段错误而崩溃。事实上,我强烈怀疑 GCC 的行为是一个错误——我尝试通过删除可变指针 ref 并采用清晰的函数式样式来明确联合成员突变,例如:
#define ALLOC_ONE(A) \
(A.__type_erased = _array_ensure_size(A.__type_erased, A.count+1),\
A.count++)
正如预期的那样,这再次与 Clang 一起工作,但再次使 GCC 崩溃。我的结论是,使用联合的高级类型操作是一个应该谨慎行事的灰色地带。
【问题讨论】:
-
对于非常好奇的人,我已经设法通过仅使用宏和完全使用联合来解决问题。如果容量超过阈值,我只使用一个复杂的宏,它使用逗号表达式重新分配()数组数据,而不是使用函数来增长数组。这适用于 GCC 和 Clang,而且速度也非常快。
标签: language-lawyer unions c11 strict-aliasing