Это безопасный способ загрузки объектов из вектора uint8_t по указателю?

#c #pointers

#c #указатели

Вопрос:

У меня есть std::vector<uint8_t> buffer ; Я хочу загрузить тип данных, который больше uint8_t (в этом примере uint16_t target , но может быть любым). Я знаю , что содержимое этого объекта было скопировано в позицию size_t ID buffer в.

Насколько я могу судить, можно скопировать target с помощью:

 uint16_t target = *((uint16_t*) amp;buffer[ID]);
 

Он работает с gcc version 9.3.0 Ubuntu с использованием C 17.

Но это, конечно, выглядит непреднамеренно. Итак, безопасно ли это использовать? То есть, пока я точно знаю, что ID sizeof(target) находится в буфере, могу ли я быть уверен, что это будет скомпилировано и запущено без сбоев во всех ОС?

Комментарии:

1. Я полагаю, что на некоторых платформах могут возникнуть проблемы с выравниванием.

Ответ №1:

Нет, это не так. Вы не можете «притворяться», что a uint16_t существует там, где его нет.

Существуют некоторые обстоятельства, при которых такое определение типа безопасно. Например, вы можете переосмыслить существующий объект нужного вам типа как последовательность байтов (через a char* , unsigned char* или std::byte* ), но обратное неверно. (ссылка)

C довольно требователен к тому, какие объекты «существуют», и вы должны следовать правилам языка, чтобы объекты существовали должным образом, прежде чем использовать их. Это не все просто байты. «Оптимизация» компилятора может нарушить предполагаемое поведение вашей программы еще до того, как мы перейдем к проблемам выравнивания и т. Д. Более низкого уровня На реальных компьютерах.

Чтобы сделать это безопасным, вам нужно будет скопировать байты в новый uint16_t :

 uint16_t target = 0;
std::copy(
   reinterpret_cast<const char*>(amp;buffer[ID]),
   reinterpret_cast<const char*>(amp;buffer[ID])   sizeof(uint16_t),
   reinterpret_cast<char*>(amp;target)
);
 

… или более короткий:

 uint16_t target = 0;
std::memcpy(amp;target, amp;buffer[ID], sizeof(uint16_t));
 

Обратите внимание, что здесь мы используем исключение сглаживания, которое я описал выше, чтобы переосмыслить a uint16_t как последовательность байтов, записывая в эту последовательность.

Итак, эти версии безопасны, если копируемые вами байты действительно образуют действительное представление a uint16_t в вашей системе (что довольно легко гарантировать для основных платформ).

Комментарии:

1. @eerorika Предоставил, что это может быть короче. Однако мне не нравится полное отсутствие безопасности типов. Я видел ошибки, когда кто-то заменял массив на a std::map , не проверяя все способы использования, и memcpy / memset операции, которые никогда не должны были быть написаны в первую очередь, молча продолжали заполнять байты, пока сбой в производстве не выявил проблему. Хотя, честно говоря, это не блестящий пример, поскольку языковые проверки этих приведений должны сначала решить проблему.

2. Не reinterpret_cast будет std::copy также «продолжать заполнять байты до сбоя». Я не понимаю, насколько они безопаснее.

3. @JVApen Да . Однако я не вижу способа использовать его для захвата байтов из некоторого подмножества массива байтов.

4. Я хотел заметить, что C 20 std::bit_cast может быть лучшей альтернативой std::memcpy , но bit_cast требует, чтобы типы источника и назначения имели одинаковый размер, чего здесь нет.

5. @JVApen Это был бы законный способ реализации std::bit_cast . Но я думаю, что это было бы немного глупо, поскольку bit_cast в основном это (четко определенный) no-op. Я проверил на MSVC, и там это реализовано как __builtin_bit_cast .