类型双关

c++作为一个强类型语言,它拥有一个类型系统 ,当我们在创建变量时必须声明整数,双精度,单精度,布尔类型或者结构体等,并且这种类型系统并不强制进行,在c++中即使类型时编译器强行执行的,但是我们可以直接访问内存。即,当我们想将一段整形类型内存当作double使用时我们可以直接访问它的内存,可以轻易饶过类型系统,如果有一个类或者结构,没有指向别处的指针,那么我们可以重新对解释整个结构或者类。在应用时,比如将一个类型变成一个字节数组写出来等。但是除非需要,否则不要轻易使用,这也是c++的效率很高的原因。

代码解释如下:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
int a = 50;
double value = a;
std::cout << value << std::endl;
}

以上例子中我们在第7行加上断点,取a地址内存时如下

image-20241002201103261

而我们的value的地址如下:

image-20241002201134523

由此我们发现此处49和40对应不上我们的数值,这是因为在程序中进行了隐式转换,实际上他不知道要转换成什么。但是还是照旧转换。

那么我们如果想将int对应的地址直接当作double来看呢?我们使用一个较为原始的方法来,如下:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
int a = 50;
double value = *(double*) &a;//此处即为类型双关
std::cout << value << std::endl;
}

此处运行结果为:

image-20241002201621771

此处我们再来看value的地址如下:

image-20241002201734325

此处我们发现double是8位,这里则有后四位均是未初始化内存。这个过程是在我们int内存之后继续增加了4个字节的位置,然后复制过来在一个新空间使用,但是虽然可以写入,但是在读取时我们依旧读取了int后那不属于自己的四位内存,这很不好。甚至当我们不想复制直接将int当double时我们可以使用引用 即

1
double& value = *(double*) &a;

此处是非常危险的,因为这样在修改时,我们会修改int后新增的4位,会导致修改不属于我们的内存。

下面使用一个更具象的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

struct Entity
{
int x, y;
};

int main()
{
Entity e = { 5,8 };

std::cin.get();
}

以上代码中我们寻址e的地址发现:

image-20241002202837041

里面存储了5,8在结构体中,如果没有任何成员等,它至少会有一个字节的大小,因为需要寻址,如果有成员变量,那么他们是相互挨在一起的。并且只有这成员变量的大小,类似数组。我们可以直接访问内存的方式得到这些值。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

#include <iostream>

struct Entity
{
int x, y;

int* GetPositions()
{
return &x;
}
};

int main()
{
Entity e = { 5,8 };

int* position = (int*)&e;
//或者 int y =*(int*)((char*)&e + 4);
std::cout << position[0] << "," << position[1] << std::endl;

//第二种方式
/*
int* position = e.GetPositions()
std::cout << position[0] << "," << position[1] << std::endl;
*/
std::cin.get();
}

如上代码我们直接通过访问内存来访问数值输出结果为:

image-20241002203300721


联合体

联合体(union)有点类似于类,但是它一次只能占用一 个成员内存,比如当我声明4个浮点数时在类中会占用4*4个字节,在union中则为4个字节。但是由于四个公用相同的内存,则改变其中一个值则其他的会跟着改变。他没有虚函数。一般和类型双关使用居多。

使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#include <iostream>

int main()
{
struct Union
{
union
{
float a;
int b;
};
};

Union u;
u.a = 2.0f;
std::cout << u.a << "," << u.b << std::endl;
std::cin.get();
}

输出结果如下:

image-20241002214214693

此处a,b公用内存,即b的值为用a的内存存储的数值用int类型解释所得。即同一内存地址被不同类型解释

下面进行一个更加直观的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>

struct Vector2
{
float x,y;
};

struct Vector4
{
union
{
struct
{
float x,y,z,w;
};
struct
{
Vector2 a,b;
};
};
};

void PrintVector(const Vector2& vector)
{
std::cout << vector.x << "," << vector.y << std::endl;
}

int main()
{
Vector4 vector = {1.0f, 2.0f, 3.0f,4.0f};
PrintVector(Vector.a);
PrintVector(Vector.b);
vector.z =500.0f
std::cout << std::endl;
PrintVector(Vector.a);
PrintVector(Vector.b);

std::cin.get();
}

运行结果如下:

image-20241002215350344

此处可以发现,a对应x则b是对应z的位置,所以a占据x地址,b占据z地址。


资料参考:

youtube上the cherno的cpp系列