什么是类型双关语,它的目的是什么?

What is type punning and what is the purpose of it?

本文关键字:是什么 类型 双关语 什么      更新时间:2023-10-16

Type punning

指针锯齿的一种形式,其中两个指针引用内存中的同一位置,但将该位置表示为不同的类型。编译器会将这两个"双关语"视为不相关的指针。类型双关可能会导致通过两个指针访问的任何数据出现依赖问题。

这篇文章想说什么? 如果我使用它或不使用它会发生什么?

正如它所说,类型双关语是指您有两个不同类型的指针,它们都指向同一位置。例:

// BAD CODE
uint32_t data;
uint32_t* u32 = &data;
uint16_t* u16 = (uint16_t*)&data; 
*u16 = ... // de-referencing invokes undefined behavior

此代码调用 C++(和 C)中的未定义行为,因为不允许通过不兼容类型的指针访问相同的内存位置(有一些特殊例外)。这被非正式地称为"严格别名冲突",因为它违反了严格的别名规则。

进行类型双关的另一种方法是通过联合:

// BAD C++ CODE
typedef union
{
uint32_t u32;
uint16_t u16 [2];
} my_type;
my_type mt;
mt.u32 = 1;
std::cout << mt.u16[0]; // access union data through another member, undefined behavior

这在C++中也是未定义的行为(但在 C 中是允许的并且完全没问题)。

类型双关语和别名是不同但相关的概念,尽管它们在很大程度上是正交的,但一些编译器编写者似乎无法区分它们。

类型双关是指将存储写入为一种类型并读取为另一种类型的情况,通常目的是允许将值解释为位序列,允许将位序列解释为值,或允许将值用作表示形式匹配的另一种类型, 至少在感兴趣的部分。 例如,后一种形式的类型双关语在以下情况下可能很有用:可能具有指向各种结构类型的指针,所有这些结构类型共享一个公共初始序列,并且可能需要对所有这些结构的公共初始序列成员进行操作,尽管结构的类型不同。 请注意,尽管该标准包含明确的保证,这表明后一种形式的类型双关语应该是有用的,但将其与别名混淆的编译器不支持此类构造。

别名是指一种不同的概念,其中使用两个或多个同时活动但看似不相关的方式以相互交互的方式访问存储。 给定如下内容:

int test1(int *p1, int *p2)
{
*p1 = 1;
*p2 = 2;
return *p1;
}

如果p1==p2,则p1p2将别名,因为p1将用于访问p2在创建和上次使用p2之间的某个时间标识的存储,在上下文中p1不可能从p2创建[有可能p1可能是在调用函数之前从p2创建的, 但是p1不可能从函数中的p2派生出来]。 但是,由于标准允许标识相同类型的左值之间的混叠,因此上述构造将在p1==p2时定义行为,尽管p1p2别名

另一方面,给定如下内容:

struct s1 {int x; };
struct s2 {int x; };
union s1s2 {struct s1 v1; struct s2 v2; } uarr[100];
int test1(int i, int j)
{
int temp;
{ struct s1 *p1 = &uarr[i].v1; temp = p1->x; }
if (temp)
{ struct s2 *p2 = &uarr[j].v2; p2->x = 1; }
{ struct s1 *p3 = &uarr[i].v1; temp = p3->x; }
return temp;
}

在这里,指针p1p2p3具有明显不相交的生存期,因此不会同时处于活动状态,并且会相互别名。 每个指针都是独立派生自uarr,每个指针的生存期将在下次使用uarr之前结束。 因此,此代码使用类型双关语来访问与struct s1struct s2相同的存储,但所写的不会利用别名,因为对相关存储的所有访问都明显来自同一个根级对象uarr

不幸的是,即使基于类型的访问规则旨在(根据基本原理和脚注)指示何时允许事物别名,一些编译器以使语言功能(如通用初始序列保证)基本上无用的方式解释它们,因为他们使用类型访问规则作为重写代码的借口,以从uarr中删除p3的派生, 从而在没有混叠的地方引入混叠。

使用双关语是有充分理由的。 假设您想通过串行链路传输数据,但数据是 实际上是不同类型的包装结构。 打包结构作为 BYTE 数组发送,但显示数据 哪些是不同类型的...

int main(void)  
{
unsigned char a[10] = {1,2,3,4,5,6,7,8,9,0};
unsigned int x,y,z;
x = *(unsigned int*) a;
y = *(unsigned int*) (a+1);
z = *((unsigned int*) a+1);
printf("x = %08X, y = %08X, z = %08Xn",x,y,z);
return 0;
}

答: x = 04030201, y = 05040302, z = 08070605

请注意,这是小端序(低内存中的LSB)