【问题标题】:C++: is reinterpret_cast the best choice in these scenarios?C++:reinterpret_cast 在这些场景中是最佳选择吗?
【发布时间】:2019-09-14 12:38:43
【问题描述】:

这一直困扰着我很长时间:如何将指针从任何东西转换为 char * 以将二进制文件转储到磁盘。

在 C 中,您甚至都不会考虑它。

double d = 3.14;
char *cp = (char *)&d;

// do what u would do to dump to disk

但是,在 C++ 中,每个人都说 C-cast 不受欢迎,我一直在这样做:

double d = 3.14;
auto cp = reinterpret_cast<char *>(&d);

现在这是从cppreference复制的, 所以我认为这是正确的方法。

但是,我从多个来源了解到这是 UB。 (例如this one) 所以我不禁想知道是否有任何“DB”方式(根据那个帖子,没有)。

我经常遇到的另一个场景是这样实现一个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,编译器编写者通常也会做一些理性的事情来确保一切正常。 我同意:这不是不合理的要求。

所以我的问题真的是,对于上面提到的两个常见任务:

  1. 真的没有“DB”方式来完成它们以满足终极 C++ 怪胎吗?
  2. 除了我一直在做的事情之外,还有什么更好的方法来完成它们吗?

谢谢!

【问题讨论】:

标签: c++ reinterpret-cast pointer-conversion


【解决方案1】:

您的serialize 实现的行为未定义,因为您违反了strict aliasing 规则。简而言之,严格的别名规则说,您不能通过指针或对不同类型的引用来引用任何对象。不过,该规则有一个主要例外:任何对象都可以通过指向 charunsigned 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];
    //}
}

这里,std::memcpy 不是将buffer 转换为指向不兼容类型的指针,而是将指向对象的指针转换为序列化为指向unsigned char 的指针。这样做不会违反严格的别名规则,并且程序的行为保持良好定义。请注意,确切的表示仍然未指定;因为这取决于您的 CPU 的字节序。

【讨论】:

  • 由于 std::copy_n 的速度慢了约 3%,您可以“链接”生成的目标指针:dest = std::copy_n(&amp;i, sizeof(i), dest);
  • @xtofl 使用std::copy,您需要从int*char* 的显式转换(std::memcpy 隐藏起来),但是是的,我怀疑两者会编译为@987654322 @
  • @MilesBudnek 感谢您的回答。不过,我需要时间消化。我确实有一些后续行动: 1. 从我的 OP 中的 SO 链接,它提到从 char * 转换为 unsigned char * 可能会使程序崩溃。这似乎与你提到的例外相矛盾。你能解释一下吗? 2. 我无法将serialize 原型更改为char *。这是否意味着我应该 static_cast void * 缓冲区到 char * 在实施中的第一件事?谢谢!
  • @MilesBudnek 嗯,另一个跟进问题:uintptr_t 方式,就像我说的,将指针转换为整数并使用sizeof 进行地址数学运算,这也是 UB 并且应该避免吗?
  • @TerryTsao 1) 我认为这个问题的前提是不正确的。出于多种原因,char* 可能会别名为unsigned char。 2) std::memcpy 接受 void* 并在内部将其转换为 unsigned char*,所以你在那里很好,但你不能在 void* 上做算术,所以你需要在一些地方做一个演员表如果您将多个对象写入缓冲区,请指出。 3)这取决于,但它可能是UB。真正重要的是在您的指针指向的位置是否存在适当类型的对象,而不是您如何获得该指针。
猜你喜欢
  • 2012-03-11
  • 2014-03-13
  • 1970-01-01
  • 2022-11-30
  • 1970-01-01
  • 1970-01-01
  • 2019-08-12
  • 2010-10-10
  • 2012-06-05
相关资源
最近更新 更多