在我最近关于字符顺序的post篇文章中,有人告诉我应该成立unions ,因为不这样做会导致UB.

我也在其他posts个网站上找到了关于这方面的讨论.

在这个post中,"严格的指针别名冲突"在不使用联合时被提到是一个问题.

In this post, it is mentioned that 8-bit types don't have to have the same memory alignment as 32 bit types, and that this affects the success of casting.
I don't understand this.

因此,我想更多地了解UB在访问8位数组成员并将它们转换为32位以通过位移位生成特定字符顺序的上下文中可能发生的情况,以及unions 如何防止这种情况.

推荐答案

在我最近关于字符顺序的帖子中,有人告诉我应该成立类型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类型的对象.

C++相关问答推荐

使用SWI—Prolog的qsave_program生成二进制文件有什么好处?'

ATmega328P EEPROM未写入

为什么net/if.h在ifaddrs.h之前?

拥有3x3二维数组并访问数组[1][3]等同于数组[2][0]?

C中函数类型的前向声明

是否可以使用指针算法在不对齐的情况下在 struct 中相同类型的字段的连续序列之间移动?

用gcc-msse 2编译的C程序包含AVX 1指令

如何将另一个数组添加到集合中,特别是字符串?

C:在编译时构建和使用字符串文字的预处理器宏?

预处理器宏扩展(ISO/IEC 9899:1999(E)§;6.10.3.5示例3)

当内存来自Malloc时,将char*转换为另一个指针类型是否违反了严格的别名规则?

是否定义了此函数的行为?

不使用任何预定义的C函数进行逐位运算

C";中的ANN运行时判断失败#2-变量outputLayer;周围的堆栈已损坏.运行后出错

为什么WcrTomb只支持ASCII?

GetText不适用于包含国际字符的帐户名称

赋值两侧的后置增量,字符指针

如何在C中处理流水线中的a、n命令?

不兼容的整数到指针转换传递';char';到类型';常量字符*

通过char*访问指针的对象表示是未定义的行为吗?