C ++:在这些情况下,reinterpret_cast是最佳选择吗?

时间:2019-04-25 06:37:04

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

这已经困扰了我很长时间:如何将指针从任何内容转换为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. 除了我一直在做的事情之外,还有其他更好的方法可以实现这些目标吗?

谢谢!

1 个答案:

答案 0 :(得分:6)

您的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];
    //}
}

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