C ++: является ли reinterpret_cast лучшим выбором в этих сценариях? - PullRequest
3 голосов
/ 25 апреля 2019

Меня это очень долго беспокоило: как выполнить преобразование указателя из чего угодно в 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.(например, этот ) Так что я не могу не задаться вопросом, существует ли вообще какой-либо путь «БД» (согласно этому посту его нет).

Другой сценарий, с которым я часто сталкиваюсь, этореализовать 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. Действительно ли нет«БД» - способ их достижения, который удовлетворит лучших фанатов С ++?
  2. Есть ли лучший способ сделать их, кроме того, что я делал?

Спасибо!

1 Ответ

6 голосов
/ 25 апреля 2019

Поведение вашей serialize реализации не определено, потому что вы нарушаете правила строго псевдонимов . Короче говоря, строгие правила псевдонимов говорят, что вы не можете ссылаться ни на один объект через указатель или ссылку на другой тип. Однако есть одно главное исключение из этого правила: на любой объект можно ссылаться через указатель на 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 к указателю на несовместимый тип, std::memcpy приводит указатель к объекту для сериализации в указатель на unsigned char. При этом строгие правила псевдонимов не нарушаются, и поведение программы остается четко определенным. Обратите внимание, что точное представление все еще не определено; так как это будет зависеть от порядка работы вашего процессора.

...