在不破坏 C99 中严格的别名规则的情况下使用 void * 键入双关语
Posted
技术标签:
【中文标题】在不破坏 C99 中严格的别名规则的情况下使用 void * 键入双关语【英文标题】:Type punning with void * without breaking the strict aliasing rule in C99 【发布时间】:2013-03-22 14:54:21 【问题描述】:我最近遇到了严格的别名规则,但我无法理解如何使用 void *
在不违反规则的情况下执行类型双关。
我知道这违反了规则:
int x = 0xDEADBEEF;
short *y = (short *)&x;
*y = 42;
int z = x;
而且我知道我可以安全地使用 C99 中的联合进行类型双关:
union
int x;
short y;
data;
data.x = 0xDEADBEEF;
data.y = 42;
int z = data.x;
但是如何使用void *
在 C99 中安全地执行类型双关语?以下是否正确:
int x = 0xDEADBEEF;
void * helper = (void *)&x;
short *y = (short *)helper;
*y = 42;
int z = x;
我怀疑代码仍然会违反严格的别名规则,因为变量x
的地址处的内存可以被x
和取消引用的y
修改。
如果通过void *
未定义类型双关语,那么C99 中void *
的用途是什么?
【问题讨论】:
【参考方案1】:void *
与双关语无关。其主要用途是:
允许不关心调用者在其中存储的对象类型的通用分配和释放操作(例如malloc
和free
)。
允许调用者通过函数传递指向任意类型的指针,该函数将通过回调将其传回(例如qsort
和pthread_create
)。在这种情况下,编译器无法强制进行类型检查; 你的责任在编写调用者和回调时确保回调访问具有正确类型的对象。
指向void
的指针也用于一些实际操作对象的地方(如memcpy
),作为对象的覆盖unsigned char []
表示。这可以被视为类型双关语,但这不是别名违规,因为char
类型允许为任何内容设置别名以访问其表示。在这种情况下,unsigned char *
也可以工作,但void *
的优点是指针会自动转换为void *
。
在您的示例中,由于原始类型是 int
而不是联合,因此没有合法的方式来键入双关并将其作为 short
访问。您可以改为将 x
的值复制到联合中,在那里执行明确定义的类型双关语,然后将其复制回来。一个好的编译器应该完全省略副本。或者,您可以将写入分解为 char
写入,然后它将是合法的别名。
【讨论】:
哦,我明白了,所以在使用void *
的情况下,程序员应该已经知道要转换的正确类型;由于使用了原始数据类型(即int *
-> void *
-> int *
),因此不会出现类型双关语。
我可能是错的,但不是只有char
可以给任何东西起别名,而不是unsigned char
?
@Arnout:不,它是任何字符类型(共有三种:char
、signed char
和 unsigned char
)。然而,该表示是根据unsigned char
定义的,实际上unsigned char
是您想要用于可移植地访问它的那个,因为签名类型具有表示唯一性和/或完整性问题,除非签名表示是二进制补码。
@R.. 你说“你可以在联合中进行类型双关”。但我在 C99 规范中发现:6.2.6.1 General "7. 当一个值存储在联合类型对象的成员中时,不对应于该成员但对应于其他成员的对象表示的字节取未指定价值观。”。换句话说,如果您存储 union.short = value;然后将其读作 union.int 的 .int 部分 union 是未指定的,与“定义明确的类型双关语”相去甚远,对吧?还是我错过了什么?
@VitBernatik:您引用的语言之所以存在,是因为在某些平台上,大商店有时比小商店便宜。例如,如果一个实现支持 8 位和 32 位存储,但不支持 16 位存储,并且程序包括一个结合了 16 位 short
和 32 位 float
的联合,则处理使用 32 位存储写入 short
成员可能比使用两个 8 位存储更有效。该标准的作者不想在有用的平台上禁止此类处理。以上是关于在不破坏 C99 中严格的别名规则的情况下使用 void * 键入双关语的主要内容,如果未能解决你的问题,请参考以下文章
调用 free() 包装器:取消引用类型双关指针将破坏严格别名规则