3

这一直困扰着我很长时间:如何进行指针转换从任何东西到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。(例如这个)所以我不禁想知道是否有任何“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来完成类似于任务的任务,但我在这里猜测它也是 UB。serializesizeof

即使他们是 UB,编译器编写者通常也会做一些合理的事情来确保一切正常。我对此表示同意:要求这不是不合理的事情。

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

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

谢谢!

4

1 回答 1

6

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

于 2019-04-25T07:17:58.697 回答