什么是类型惩罚&它的目的是什么?如果我使用它或不使用它会发生什么?

时间:2017-05-23 14:21:00

标签: c++ casting type-conversion type-punning

  

输入双关语

     

一种指针别名形式,其中两个指针指向内存中的相同位置,但将该位置表示为不同类型。编译器会将“双关语”视为不相关的指针。类型punning有可能导致通过两个指针访问的任何数据的依赖性问题。

文章试图说什么?

3 个答案:

答案 0 :(得分:6)

正如它所说,类型惩罚是当你有两个不同类型的指针时,都指向同一个位置。例如:

// BAD CODE
uint32_t data;
uint32_t* u32 = &data;
uint16_t* u16 = (uint16_t*)&data; // undefined behavior

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

进行打字的另一种方式是通过工会:

// 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中允许完全正常)。

答案 1 :(得分:1)

完全有理由使用双关。 假设您要通过串行链路传输数据,但是数据是 实际上是不同类型的打包结构。 打包的结构作为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 = %08X\n",x,y,z);

    return 0;
}

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

请注意,这是小尾​​数(低位内存中的LSB)

答案 2 :(得分:0)

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

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

别名是一个不同的概念,其中使用两个或多个同时活动但看似无关的方式以相互交互的方式访问存储。给出类似的东西:

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尽管 p1和{{1} }别名。

另一方面,给出类似以下内容的

p2

在这里,指针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; } p1p2的生命周期显然是不相交的,因此不会同时处于活动状态并且彼此互为别名。每个指针均独立于p3派生,每个指针的生存期将在下次使用uarr之前结束。因此,此代码利用类型punning来访问与uarrstruct s1相同的存储,但是如前所述,由于对相关存储的所有访问显然都是从中派生的,因此并未编写别名。相同的根级对象struct s2

不幸的是,即使意图使用基于类型的访问规则(根据原理和脚注)来指示何时允许 进行别名,但某些编译器仍以使语言具有特征的方式来解释它们诸如Common Initial Sequence的保证基本上是无用的,因为它们使用类型访问规则作为改写代码的借口,从而从uarr中删除了p3的派生,从而引入了别名。没有的地方。