C 联合类型双关数组
鉴于以下代码,我有一些与类型双关相关的问题。我看不出这没有违反严格的别名规则,但我无法指出具体的违规行为。我最好的猜测是,将联合成员传递到函数中违反了严格的别名。
以下代码位于编译器资源管理器上。
#include <stdint.h>
union my_type
{
uint8_t m8[8];
uint16_t m16[4];
uint32_t m32[2];
uint64_t m64;
};
int func(uint16_t *x, uint32_t *y)
{
return *y += *x;
}
int main(int argc, char *argv[])
{
union my_type mine = {.m64 = 1234567890};
return func(mine.m16, mine.m32);
}
我的观察:
- 假设 func 的参数不互相别名,则 func 不违反严格别名。
- 在 C 中,允许使用
union
进行类型双关。 - 将
m16
和m32
传递到func
必须违反某些规定。
我的问题:
- 像这样的数组类型双关有效吗?
- 我通过将指针传递给
func
到底违反了什么? - 在这个例子中我还遗漏了哪些其他问题?
Given the following code, I have some questions related to type punning. I do not see any way that this isn't violating strict aliasing rules, but I cannot point to the specific violation. My best guess is that passing the union members into the function violates strict aliasing.
The following code is on Compiler Explorer.
#include <stdint.h>
union my_type
{
uint8_t m8[8];
uint16_t m16[4];
uint32_t m32[2];
uint64_t m64;
};
int func(uint16_t *x, uint32_t *y)
{
return *y += *x;
}
int main(int argc, char *argv[])
{
union my_type mine = {.m64 = 1234567890};
return func(mine.m16, mine.m32);
}
My observations:
- Assuming the arguments to
func
do not alias each other,func
does not violate strict aliasing. - In C, it is permissible to use a
union
for type punning. - Passing
m16
andm32
intofunc
must violate something.
My questions:
- Is type punning with arrays like this valid?
- What exactly am I violating by passing the pointers into
func
? - What other gotchas am I missing in this example?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
违反的规则是 C 2018 6.5.16.1 3:
具体来说,在
*y += *x
中,存储在y
指向的对象mine.m16
中的值是从另一个对象mine.m32
与mine.m16
的存储重叠,但重叠并不精确,而且无论限定符如何,这些对象也不具有兼容的类型。请注意,此规则适用于简单分配,如
E1 = E2
中所示,而代码具有组件分配,E1 += E2
。然而,复合赋值E1 += E2
在 6.5.16.2 3 中定义为等价于E1 = E1 + E2
,除了左值E1
> 仅评估一次。是的,C 标准允许通过联合成员使用别名;读取最后存储的成员以外的成员将重新解释新类型中的字节。然而,如果程序的行为是由 C 标准定义的,特别是上面引用的规则,这并不能免除程序遵守其他规则的责任。
传递指针不会违反任何规则。正如上面所回答的,使用指针的赋值违反了规则。
如果我们更改
func
:则 6.5.16.1 3 中的规则不适用,因为不存在涉及重叠对象的赋值。并且不违反 6.5 7 中的别名规则,因为
*y
是一个定义为用于访问它的类型的对象,uint16_t
和*x 是一个对象,定义为用于访问它的类型,
uint32_t
。然而,如果这个函数被单独翻译(没有可见的union定义),编译器可以假设*x和*y执行不重叠,因此它可能会缓存由*y += 1;
生成的*y
值并返回该缓存值,而忽略* x += 1;
变化<代码>*y。这是C标准的一个缺陷。The rule violated is C 2018 6.5.16.1 3:
Specifically, in
*y += *x
, the value being stored in the object pointed to byy
,mine.m16
, is read from another object,mine.m32
, that overlaps the storage ofmine.m16
, but the overlap is not exact and neither do the objects have compatible types, regardless of qualifiers.Note that this rule is for simple assignment, as in
E1 = E2
, whereas the code has a component assignment,E1 += E2
. However, the compound assignmentE1 += E2
is defined in 6.5.16.2 3 to be equivalent toE1 = E1 + E2
except that the lvalueE1
is evaluated only once.Yes, the C standard allows aliasing via union members; reading a member other than the last one stored will reinterpret the bytes in the new type. However, this does not absolve a program of conforming to other rules if its behavior is to be defined by the C standard, notably the rule quoted above.
No rule is violated by passing the pointers. The assignment using the pointers violates a rule, as answered above.
If we change
func
:then the rule in 6.5.16.1 3 does not apply, as there is no assignment involving overlapping objects. And the aliasing rules in 6.5 7 are not violated, as
*y
is an object defined as the type used to access it,uint16_t
, and*x
is an object defined as the type used to access it,uint32_t
. Yet, if this function is translated in isolation (without theunion
definition visible), the compiler is permitted to assume*x
and*y
do not overlap, so it may cache the value of*y
produced by*y += 1;
and return that cached value, in ignorance of the fact that*x += 1;
changes*y
. This is a defect in the C standard.func(uint16_t *x, uint32_t *y)
可以自由地假设*x
和*y
不重叠,因为x, y
是足够不同的指针类型。由于引用的数据在OP的代码中确实重叠,所以我们遇到了问题。有关
union
和别名的特殊问题在func()
主体中不适用,因为调用代码的union-ness 是丢失的。替代的“安全”代码可能是:
将指针传递给函数
func()
不必考虑的重叠数据。我不认为这是一个数组或联合问题,只是将指针传递给函数 func() 没有义务考虑的重叠数据之一。
次要问题:
int
可能是 16 位,可能会导致在uint32_t
到int
转换中出现实现定义的行为。考虑
fun1()
之间的差异就必须考虑重叠的可能性。
fun2()
不会。func(uint16_t *x, uint32_t *y)
is free to assume*x
and*y
do not overlap asx, y
are different enough pointer types. Since the referenced data does overlap in OP's code, we have a problem.The special issues about
union
s and aliasing do not apply here in the body offunc()
as the union-ness of the calling code is lost.Alternate "safe" code could have been:
Passing pointers to overlapping data that the function
func()
is not obliged to account for.I do not see this as an array or
union
issue, just one of passing pointers to overlapping data that the functionfunc()
is not obliged to account for.Minor:
int
may be 16-bit, potentially causing implementation defined behavior in the conversion ofuint32_t
toint
.Consider the difference between
fun1()
would have to consider an overlap potential.fun2()
would not.不可靠。所谓的严格别名规则是根据用于访问给定对象的左值(相对于该对象的有效类型)来表达的。
func()
的两个参数不需要互相别名来执行func()
来产生严格别名冲突。示例:围绕函数参数相互别名的问题将属于您没有使用的
restrict
限定指针的范围。是的,只要双关是通过 union 对象执行的。 C17 6.5/7(上述严格别名规则)涵盖了这一点:
请注意,这与被访问的存储实际上是在联合对象内部,而是关于用于访问它的左值类型相对于被访问对象的实际(有效)类型。
确实如此,尽管语言规范可能比实际情况更清楚。然而,它确实说:
(C17 6.7.2.1/16)
在您的特定示例中,
mine.m16
和mine.m32
都没有调用时存储在其中的值,但在任何情况下,最多其中一个可以有值。当 func 尝试读取存储在这些对象中的值时,结果未定义(因为它们实际上没有存储值)。规范中包含的第 6.5.2.3/6 段支持了这种解释:
如果通常可以访问随机联合成员,而不管哪个成员实际上存储了值,则不需要这样的特殊规定。
不是那样的,不。规范允许的数组类型双关还有其他变体。
调用本身并没有违反任何内容。获取联合成员的地址是合法的,即使当前没有存储值,并且将结果指针值传递给函数也是合法的。但是,当使用这些参数调用时,函数在尝试取消引用一个或两个指针时会犯严格别名冲突,如上所述。
与您的其他答案之一相反,所提供的代码并不违反第 6.5.16.1/3 段。存储在
*y
中的值不是从重叠对象*x
读取的,而是该值与原始值的总和<代码>*y。该总和是计算得出的,而不是从对象中读取的,因此6.5.16.1/3
不适用。但您可能会忽略,如果func()
执行简单的赋值而不是加号,就会违反6.5.16.1/3
。Not reliably true. The so-called strict-aliasing rule is expressed in terms of the lvalue used to access a given object, relative to that object's effective type. The two arguments to
func()
do not need to alias each other for execution offunc()
to produce a strict-aliasing violation. Example:Issues revolving around function parameters aliasing each other would be the realm of
restrict
-qualified pointers, which you are not using.Yes, provided that the punning is performed via the union object. This is covered by C17 6.5/7, the aforementioned strict-aliasing rule:
Note well that this isn't about the storage being accessed actually being inside a union object, but rather about the type of lvalue used to access it relative to the actual (effective) type of the object being accessed.
It does, though the language specification could be a lot clearer about that than it is. It does, however, say:
(C17 6.7.2.1/16)
In your particular example, neither
mine.m16
normine.m32
has a value stored in it at the time of the call, but under any circumstance, at most one of them could have a value. Whenfunc
then tries to read the values stored in those objects the results are not defined (because they don't actually have values stored in them).That interpretation is supported by the inclusion in the spec of paragraph 6.5.2.3/6:
No such special provision would be needed if it were generally ok to access random union members regardless of which one actually had a value stored.
Not like that, no. There are other, variations on array type-punning that are allowed by the spec.
The call itself does not violate anything. It is legal to take the address of a union member, even one that does not currently have a value stored in it, and it is legal to pass the resulting pointer values to functions. But when called with those arguments, the function commits strict-aliasing violations when it attempts to dereference one or both pointers, as described above.
Contrary to one of your other answers, the code presented does not run afoul of paragraph 6.5.16.1/3. The value being stored in
*y
is not read from overlapping object*x
, but rather is the sum of that value with the original value of*y
. That sum is computed, not read from an object, so6.5.16.1/3
does not apply. But you may be missing that it would violate6.5.16.1/3
iffunc()
performed a simple assignment instead of a plussignment.