C++:reinterpret_cast 是这些场景中的最佳选择吗?
C++: is reinterpret_cast the best choice in these scenarios?
这困扰了我很长时间:如何将指针从任何内容转换为 char *
以将二进制文件转储到磁盘。
在C中,你想都别想。
double d = 3.14;
char *cp = (char *)&d;
// do what u would do to dump to disk
然而,在每个人都说 C-cast 不受欢迎的 C++ 中,我一直在这样做:
double d = 3.14;
auto cp = reinterpret_cast<char *>(&d);
现在这是从 cppreference 复制的,
所以我认为这是正确的方法。
但是,我从多个来源了解到这是 UB。
(例如 this one)
所以我不禁想知道是否有任何 "DB" 方式(根据那个 post,有 none)。
我经常遇到的另一种情况是像这样实现一个API:
void serialize(void *buffer);
你会把很多东西转储到这个缓冲区。现在,我一直在这样做:
void serialize(void *buffer) {
int intToDump;
float floatToDump;
int *ip = reinterpret_cast<int *>(buffer);
ip[0] = intToDump;
float *fp = reinterpret_cast<float *>(&ip[1]);
fp[0] = floatToDump;
}
嗯,我想这也是UB。
现在,真的没有 "DB" 方法来完成这些任务吗?
我看到有人使用 uintptr_t
来完成类似于 serialize
的任务,其中指针作为整数数学以及 sizeof
,
但我在这里猜测它也是 UB。
即使它们是 UB,编译器编写者通常也会做一些理性的事情来确保一切正常。
我同意这一点:要求这不是一件不合理的事情。
所以我的问题确实是,对于上面提到的两个常见任务:
- 真的没有 "DB" 方法来完成它们以满足最终的 C++ 怪胎吗?
- 除了我一直在做的,还有什么更好的方法来完成它们?
谢谢!
您的 serialize
实施行为未定义,因为您违反了 strict aliasing 规则。简而言之,严格的别名规则表示您不能通过指针或对不同类型的引用来引用任何对象。不过该规则有一个主要例外:任何对象都可以通过指向 char
、unsigned char
或(C++17 起)std::byte
的指针来引用。请注意,此例外不适用于相反的情况; char
数组不能通过指向 char
.
以外的类型的指针访问
这意味着您可以通过这样更改 serialize
函数来使其定义明确:
void serialize(char* buffer) {
int intToDump = 42;
float floatToDump = 3.14;
std::memcpy(buffer, &intToDump, sizeof(intToDump));
std::memcpy(buffer + sizeof(intToDump), &floatToDump, sizeof(floatToDump));
// Or you could do byte-by-byte manual copy loops
// i.e.
//for (std::size_t i = 0; i < sizeof(intToDump); ++i, ++buffer) {
// *buffer = reinterpret_cast<char*>(&intToDump)[i];
//}
//for (std::size_t i = 0; i < sizeof(floatToDump); ++i, ++buffer) {
// *buffer = reinterpret_cast<char*>(&floatToDump)[i];
//}
}
在这里,buffer
不是将 buffer
转换为指向不兼容类型的指针,而是将 std::memcpy
转换为指向对象的指针以序列化为指向 unsigned char
的指针。这样做不会违反严格的别名规则,并且程序的行为仍然是明确定义的。请注意,确切的表示形式仍未指定;因为这将取决于您的 CPU 字节顺序。
这困扰了我很长时间:如何将指针从任何内容转换为 char *
以将二进制文件转储到磁盘。
在C中,你想都别想。
double d = 3.14;
char *cp = (char *)&d;
// do what u would do to dump to disk
然而,在每个人都说 C-cast 不受欢迎的 C++ 中,我一直在这样做:
double d = 3.14;
auto cp = reinterpret_cast<char *>(&d);
现在这是从 cppreference 复制的, 所以我认为这是正确的方法。
但是,我从多个来源了解到这是 UB。 (例如 this one) 所以我不禁想知道是否有任何 "DB" 方式(根据那个 post,有 none)。
我经常遇到的另一种情况是像这样实现一个API:
void serialize(void *buffer);
你会把很多东西转储到这个缓冲区。现在,我一直在这样做:
void serialize(void *buffer) {
int intToDump;
float floatToDump;
int *ip = reinterpret_cast<int *>(buffer);
ip[0] = intToDump;
float *fp = reinterpret_cast<float *>(&ip[1]);
fp[0] = floatToDump;
}
嗯,我想这也是UB。
现在,真的没有 "DB" 方法来完成这些任务吗?
我看到有人使用 uintptr_t
来完成类似于 serialize
的任务,其中指针作为整数数学以及 sizeof
,
但我在这里猜测它也是 UB。
即使它们是 UB,编译器编写者通常也会做一些理性的事情来确保一切正常。 我同意这一点:要求这不是一件不合理的事情。
所以我的问题确实是,对于上面提到的两个常见任务:
- 真的没有 "DB" 方法来完成它们以满足最终的 C++ 怪胎吗?
- 除了我一直在做的,还有什么更好的方法来完成它们?
谢谢!
您的 serialize
实施行为未定义,因为您违反了 strict aliasing 规则。简而言之,严格的别名规则表示您不能通过指针或对不同类型的引用来引用任何对象。不过该规则有一个主要例外:任何对象都可以通过指向 char
、unsigned char
或(C++17 起)std::byte
的指针来引用。请注意,此例外不适用于相反的情况; char
数组不能通过指向 char
.
这意味着您可以通过这样更改 serialize
函数来使其定义明确:
void serialize(char* buffer) {
int intToDump = 42;
float floatToDump = 3.14;
std::memcpy(buffer, &intToDump, sizeof(intToDump));
std::memcpy(buffer + sizeof(intToDump), &floatToDump, sizeof(floatToDump));
// Or you could do byte-by-byte manual copy loops
// i.e.
//for (std::size_t i = 0; i < sizeof(intToDump); ++i, ++buffer) {
// *buffer = reinterpret_cast<char*>(&intToDump)[i];
//}
//for (std::size_t i = 0; i < sizeof(floatToDump); ++i, ++buffer) {
// *buffer = reinterpret_cast<char*>(&floatToDump)[i];
//}
}
在这里,buffer
不是将 buffer
转换为指向不兼容类型的指针,而是将 std::memcpy
转换为指向对象的指针以序列化为指向 unsigned char
的指针。这样做不会违反严格的别名规则,并且程序的行为仍然是明确定义的。请注意,确切的表示形式仍未指定;因为这将取决于您的 CPU 字节顺序。