跳转至

66 C++的类型双关

1. 类型系统

Type punning(类型双关)只是一个花哨的术语,用来在 C++中绕过类型系统。C++是强类型语言,也就是说它有一个类型系统,不像 JavaScript 那样创建变量不需要声明变量类型,但 C++中你创建变量时必须声明整数、双精度数、结构体等等类型。然而这种类型系统并不像 Java 中那么“强制”,C++中虽然类型是由编译器强制执行的,但你可以直接访问内存,所以可以很容易地绕过类型系统,你是否要这么做取决于你的实际需求。在某些情况下,你绝对不应该规避类型系统,因为类型系统存在是有原因的,除非你有充分的理由,否则你不会想过多地使用它。 ^4d9dfe

假设我有一个简单的类,现在想把它写成一个字节流,就可以重新解释它的整个结构,将它作为一个字节数组然后用字节流输出出来。很多情况下这这是非常有用的,这是一种原始的、底层的访问,这就是为什么 C++效率高,应用程序性能好的原因了。

#include <iostream>

int main()
{
    int a = 50;
    double value = a;

    std::cout << value << std::endl;


    std::cin.get();
}

内存中查看 a:

内存中查看 value:

这个例子中是一个隐式转换,显式转换只需要改为: ^f3904d

double value = (double)a;

那如何取 a 的那段内存,让这段地址被当做double来看待呢?

double value = *(double*)&a; //原始方法:对a取地址,此时变为int指针,将类型修改为double指针后解引用

查看 value 的地址,因为 double 有 8 个字节,所以剩下的是未初始化的内存。

导致发生的原因是我们在上面的类型转换做的很糟糕,因为它们的大小不同,我们取了一个 4 字节的 int,然后定为 double。我们在这里做的是先把一个 int 型指针转换为了一个 double 型指针,再解引用,它实际上是在我们的 int 后继续了 4 个字节然后获取了这部分内存,它并不是我们用来存放 a 的内存。这很糟糕,在某些情况下甚至会导致崩溃。 这里的意思是我们已经把内存复制到了一个新的 double 块中操作是安全的,但是读取了不属于我们的内存是不好的。

如果你不想新创建一个变量,只是想把这个 int 当做 double 来访问,只需要再 double 后加一个&,引用而不拷贝,这样你就可以编辑 int 的内存,这是很危险的,因为 double 需要 8 个字节而我们的空间只有 4 个字节,这可能会导致程序崩溃。

1. 结构体类型转换

struct Entity
{
    int x, y;
};

int main()
{
    Entity e = { 5,8 };
    std::cin.get();
}

在内存中,这个结构体其实就是由 2 个 int 组成的,就是这两个整数 x,y。

结构体本身不包含任何类型的填充,任何类型的数据,如果是一个空的结构体,那么它至少是 1 个字节,因为我们需要对这段内存进行寻址,但如果结构体中有变量,比如这个 int x 和 y,那它就只有这两个整数。因此我们可以将 Entity 结构体看成一个 int 数组,并且不用 e.x,e.y 这种方法读取这些整数。

int* position = (int*)&e;
std::cout << position[0] << "," << position[1] << std::endl;

因为我们将其转换成了数组,所以可以像访问数组那样访问它。

同理的更疯狂的操作: 可以看出 C++是一种强大的语言的一个重要原因就是它可以自如地操纵内存。