Я использую массив char* для хранения разных типов данных, как в следующем примере:
int main()
{
char* arr = new char[8];
*reinterpret_cast<uint32_t*>(&arr[1]) = 1u;
return 0;
}
Компиляция и запуск с clang UndefinedBehaviorSanitizer сообщит о следующей ошибке:
runtime error: store to misaligned address 0x602000000011 for type 'uint32_t' (aka 'unsigned int'), which requires 4 byte alignment
Я полагаю, что мог бы сделать это по-другому, но почему это поведение undefined? Какие понятия здесь задействованы?
Указатель uint32_t должен быть выровнен
Возможный дубликат Как выровнять указатель
Вы не можете привести произвольное char*
к uint32_t*
, даже если оно указывает на массив, достаточно большой, чтобы вместить uint32_t
Есть несколько причин, почему.
uint32_t
вообще любит выравнивание по 4 байта: его адрес должен быть кратен 4.
char
такого ограничения нет. Может жить по любому адресу.
Это означает, что произвольный char*
вряд ли будет правильно выровнен для uint32_t
.
Помимо проблемы с выравниванием, ваш код демонстрирует неопределенное поведение, потому что вы нарушаете строгие правила алиасинга. По адресу, на который вы пишете, не существует объекта uint32_t
, но вы обращаетесь с ним так, как будто он там есть.
В общем, в то время как char*
может использоваться для указания на любой объект и чтения его байтового представления, T*
для любого заданного типа T
, не можем может использоваться для указания на массив байтов и записи в него байтового представления объекта.
Независимо от причины ошибки, способ ее устранения одинаков:
Если вы не заботитесь об обработке байтов как uint32_t
и просто сериализуете их (например, для отправки по сети или записи на диск), вы можете std::copy
байты в буфер:
char buffer[BUFFER_SIZE] = {};
char* buffer_pointer = buffer;
uint32_t foo = 123;
char* pfoo = reinterpret_cast<char*>(&foo);
std::copy(pfoo, pfoo + sizeof(foo), buffer_pointer);
buffer_pointer += sizeof(foo);
uint32_t bar = 234;
char* pbar = reinterpret_cast<char*>(&bar);
std::copy(pbar, pbar + sizeof(bar), buffer_pointer);
buffer_pointer += sizeof(bar);
// repeat as needed
Если вы делать хотите обрабатывать эти байты как uint32_t
(например, если вы реализуете std::vector
-подобную структуру данных), вам необходимо убедиться, что буфер правильно выровнен, и использовать новое размещение:
std::aligned_storage_t<sizeof(uint32_t), alignof(uint32_t)> buffer[BUFFER_SIZE];
uint32_t foo = 123;
uint32_t* new_uint = new (&buffer[0]) uint32_t(foo);
uint32_t bar = 234;
uint32_t* another_new_uint = new (&buffer[1]) uint32_t(foo);
// repeat as needed
Это очень исчерпывающий и хорошо объясненный ответ, спасибо!
Чтобы быть педантичным, вы можете сделать слепок; строгое правило псевдонимов не нарушается до тех пор, пока вы не попытаетесь прочитать или записать, разыменовав результат приведения. Если выравнивание неправильное, то результат приведения не указан. Если выравнивание правильное, вы можете сделать приведение, а затем использовать memcpy
, например, с 32-битными единицами измерения.
uint32_t*
соответствует адресам, которые являются множителями 4, например. 0, 4, 8 и т. д. И теперь вы просите преобразовать адрес, скажем, 5, вuint32_t*
. Что должно произойти?