在我最近关于字符顺序的帖子中,有人告诉我应该成立类型unions ,因为不这样做会导致UB.
不是的.您被告知使用联合是one way来完成您想做的事情,而不调用UB.这不是only条路.
在不使用联合时,"严格的指针别名冲突"被认为是一个问题.
通常的术语是"严格别名",而不是"严格指针别名".适当地使用联合是避免严格别名冲突的一种方法,但不是唯一的方法.
这里提到,8位类型不必与32位类型具有相同的内存对齐方式,这会影响强制转换的成功.
是.
我不明白这是怎么回事.
所以我想更多地了解UB可以
在访问8位数组成员和强制转换
将它们转换为32位,以通过位移位生成一定的字符顺序,以及
成立unions 是如何阻止它的.
首先要理解的是,在这种情况下,"未定义的行为"意味着the C language specification不定义行为.这并不意味着程序在您的计算机上的行为一定会与您预期的不同(尽管它可能会做得很好).这并不意味着编译器必须拒绝程序,或者程序必须在运行时诊断错误,或者诸如此类--所有这些都是可能的,但requiring一个或多个将使defined行为.
C的目标是适合在几乎所有的数字计算机上实现,尤其是在裸机上--毕竟,它被设想为一种编写操作系统的语言.规范明确指出具有未定义行为的许多项都与各种机器的实际行为差异有关,无论是过go 、现在还是假设的future .
您上一篇文章中的代码格式如下:
uint8_t buffer[4];
// ... assign array element values ...
switch (*((uint32_t *)buffer)) {
// ...
这个问题涉及到语言规范的两个主要条款.我将引用C17的话,但到目前为止,所有版本的语言规范都有与这些条款基本相同的版本.首先考虑强制转换问题,因为它很简单,C17 6.3.2.3/7段是允许在对象指针类型之间进行转换的主要条款.它说:
指向对象类型的指针可以被转换为指向不同对象类型的指针.如果不是If the resulting
pointer is not correctly aligned for the referenced type, the behavior is undefined.,
当再次转换回来时,结果应与原始指针相等.当指向的指针
对象被转换为指向字符类型的指针,结果指向寻址最低的字节
该对象的.结果的连续增量(直到对象的大小)产生指向
对象的剩余字节数.
(重点增加了.)
因此,当人们告诉您,将uint8_t *
赋给类型uint32_t *
可能会因为不匹配的对齐而产生UB时,这直接来自语言规范.
语言规范的另一个主要相关部分是所谓的"严格别名规则",在C17中是第6.5/7段:
对象的存储值只能由左值访问
具有以下类型之一的表达式:
- 与对象的有效类型兼容的类型,
- a qualified version of 与对象的有效类型兼容的类型,
- 作为对应于对象的有效类型的有符号类型或无符号类型的类型,
- 作为与对象的有效类型的限定版本相对应的有符号或无符号类型的类型,
- 在其成员中包含上述类型之一的聚合或联合类型(递归地包括
子聚合或包含并集),或
- 一种字符类型.
脚注89解释说
此列表的目的是指定对象在哪些情况下可能会或可能不会出现锯齿.
这就是为什么即使您的转换(uint32_t *)buffer
具有定义良好的行为,您仍然可以通过try 通过产生的指针access对象来调用UB.所讨论的左值*((uint32_t *)buffer)
具有类型uint32_t
,并且这不满足用于访问uint8_t
或此类数组的严格别名规则中给出的任何替换.(不要被"有效类型"一词搞糊涂.除非涉及动态内存分配,否则您可以简单地将"有效类型"理解为"类型".)
另一方面,请注意,严格的别名规则明确允许通过联合进行访问.此外,联合方法还解决了对齐问题,因为联合的对齐要求至少必须与其最严格对齐的成员的要求一样严格.
如果仔细研究,您可能还会发现,至少有一种方法可以在不涉及联合的情况下工作:声明一个由uint8_t
组成的数组并通过uint32_t *
访问它,而是声明一个uint32_t
并通过uint8_t *
访问它的字节.这确实假设uint8_t
是一种字符类型,规范并不保证这一点,但在实践中,如果您的实现提供了uint8_t
,那么它将是unsigned char
的别名,或者可能是char
的别名.
另一种不依赖于uint8_t
作为字符类型的方法涉及通过memcpy()
函数将uint8_t
数组的内容转换为uint32_t
类型的对象.