C++: является ли reinterpret_cast лучшим выбором в этих сценариях?

Это очень долго мучало меня: как сделать преобразование указателя из чего угодно char * записать двоичный файл на диск.

В Си ты даже не думаешь об этом.

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.

Теперь, действительно ли нет способа "БД" для выполнения любой из этих задач? Я видел, кто-то использует uintptr_t выполнить что-то подобное serialize задача с указателем в виде целочисленной математики вместе с sizeof, но я предполагаю, что это также UB.

Даже при том, что они - UB, авторы компилятора обычно делают рациональные вещи, чтобы удостовериться, что все в порядке. И я в порядке с этим: это не лишняя вещь, которую нужно просить.

Итак, мои вопросы действительно касаются двух общих задач, упомянутых выше:

  1. Неужели не существует способа "БД" для их достижения, который бы удовлетворил всех фанатов С ++?
  2. Есть ли лучший способ сделать их, кроме того, что я делал?

Спасибо!

1 ответ

Решение

Ваш 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, При этом строгие правила псевдонимов не нарушаются, и поведение программы остается четко определенным. Обратите внимание, что точное представление все еще не определено; как это будет зависеть от вашего процессора

Другие вопросы по тегам