0%

Story about type punning

What is type punning

Type Punning是指用不同類型的Pointer,指向同一塊Memory address的行為,這是Undefined beahvior,可能會造成未知的錯誤.
例如

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

int main() {
float f = 3.14;
int* pi = (int*)&f;
*pi = 42;
std::cout << "f = " << f << std::endl;
return 0;
}

Type punning違反了Strict aliasing rule

Example

寫網路程式的時候常常會遇到這種情形,分配一塊記憶體,然後Cast成另外一種Type的Pointer填值

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
typedef struct Msg
{
unsigned int a;
unsigned int b;
} Msg;

void SendWord(uint32_t);

int main(void)
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));

// Alias that buffer through message
Msg* msg = (Msg*)(buff);

// Send a bunch of messages
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0]);
SendWord(buff[1]);
}
}

Solution

C Solution

union

C語言的話可以使用union

1
2
3
4
union {
Msg msg;
unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)];
};
char*

或是使用(unisnged / signed) char *取代上面的int*
可以認為j從char*轉匯成type *是合法的,反之不成立

memcpy
1
2
3
int x = 42; 
float y;
std::memcpy(&y, &x, sizeof(x));

這樣是合法的,不過缺點就是要多一次拷貝

C++ Solution

bit_cast

C++20引進的新東西,不過實作也就只是上面的memcpy包裝

1
2
3
4
5
6
7
template <class To, class From>
bit_cast(const From& src) noexcept
{
To dst;
std::memcpy(&dst, &src, sizeof(To));
return dst;
}
std::start_lifetime_as

C++23引進的新觀念,類似於reinterpret_cast,不過沒有undefined behaviro的副作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ProtocolHeader {
unsigned char version;
unsigned char msg_type;
unsigned char chunks_count;
};

void ReceiveData(std::span<std::byte> data_from_net) {
if (data_from_net.size() < sizeof(ProtocolHeader)) throw SomeException();
const auto* header = std::start_lifetime_as<ProtocolHeader>(
data_from_net.data()
);
switch (header->type) {>
// ...
}
}

Reference