Какой инвариант делает чтение значения через raw pointer некорректным, даже если адрес охватывает достаточно байт?
Основной инвариант — выравнивание: адрес raw pointer должен соответствовать требованиям выравнивания типа T. Если адрес невыровнен, обычное разыменование указателя может привести к неопределённому поведению, даже когда в памяти физически находятся все нужные байты.
Для намеренно невыровненного доступа используют std::ptr::read_unaligned. Он снимает требование выравнивания, но не отменяет остальные требования: память должна быть доступна для чтения, указатель должен быть корректным для этой области, а байты должны образовывать допустимое значение типа T.
Выравнивание — требование аппаратных архитектур и ABI. Некоторые процессоры не поддерживают невыровненные загрузки напрямую, а другие выполняют их медленнее или с особыми ограничениями.
Rust делает выравнивание частью контракта обычных ссылок и операций чтения. Это позволяет компилятору генерировать эффективный код и оптимизировать обращения к памяти, не предполагая, что указатель может иметь произвольный адрес.
При этом системное программирование регулярно работает с упакованными структурами, сетевыми пакетами и бинарными буферами. Для таких случаев нужны явно обозначенные операции, например read_unaligned, которые позволяют локально ослабить именно требование выравнивания, не отключая остальные проверки безопасности.
Предположим, два байта в буфере должны быть интерпретированы как u16, но нужное поле начинается с нечётного смещения. Адрес поля тогда может быть невыровнен для u16, хотя диапазон памяти содержит оба байта полностью.
Преобразование адреса к *const u16 само по себе не исправляет выравнивание. Обычное чтение через такой указатель нарушает контракт операции: результатом может быть неопределённое поведение, а не просто переносимое исключение или ошибка.
Нельзя считать достаточной проверкой условие «в буфере хватает байт». Нужно отдельно проверять выравнивание либо использовать операцию, специально предназначенную для невыровненного чтения.
Обычная операция чтения значения типа T через raw pointer предполагает, что указатель выровнен как минимум по align_of::<T>(). Кроме того, область должна быть доступна для чтения на размер size_of::<T>(), память должна быть инициализирована, а получившиеся байты — представлять допустимое значение T.
std::ptr::read_unaligned предназначена для случая, когда выравнивание не гарантировано. Она читает значение побайтно с учётом требований платформы и не создаёт ссылки на невыровненную память.
Здесь срез гарантирует наличие как минимум двух байт начиная с позиции 1, но не гарантирует выравнивание для u16. Поэтому применяется read_unaligned. Порядок байт в примере — машинный; при разборе сетевого или файлового формата его нужно явно преобразовать согласно формату.
read_unaligned не делает raw pointer безопасным автоматически. Она не проверяет границы, время жизни объекта, валидность provenance или допустимость битового представления типа. Например, произвольные байты не всегда можно безопасно прочитать как bool, char, ссылку или другой тип с ограниченным набором допустимых представлений.
Создание ссылки не является альтернативой: ссылка Rust также обязана быть правильно выровнена. Даже временная ссылка на невыровненное поле нарушает инвариант и может привести к неопределённому поведению. Если данные нужно хранить дольше одного чтения, обычно безопаснее скопировать их в корректно выровненную переменную или собрать значение из байтов.
Драйвер получает от внешнего устройства бинарный пакет, в котором поле длины u16 начинается с нечётного смещения. Прямое разыменование приведённого к *const u16 указателя выглядит кратко, но опирается на неверное предположение о выравнивании.
Вариант с read_unaligned сохраняет одинарное чтение и подходит, если отдельно доказаны границы, инициализация и допустимость значения. Его минус — необходимость внимательно поддерживать unsafe-контракт и учитывать порядок байт.
Вариант с копированием двух байт в локальный массив и последующим from_le_bytes или from_be_bytes обычно проще для протоколов. Он явно задаёт порядок байт и не требует чтения u16 из невыровненной памяти, но может быть менее удобен для большого числа полей и требует описывать разбор каждого значения.
На практике для сетевого протокола выбирают копирование с явным порядком байт, а read_unaligned — когда формат уже описан в терминах машинных значений и важна минимизация промежуточных преобразований. В обоих случаях unsafe-участок делают минимальным, а проверки длины и формата оставляют в безопасной части кода.
*const T, чтобы чтение стало корректным?Нет. Приведение меняет только тип указателя и не проверяет ни выравнивание, ни границы, ни время жизни памяти, ни допустимость значения T. Все эти свойства должны быть доказаны отдельно перед unsafe-операцией.
read_unaligned требования к инициализации и допустимому представлению типа?Нет. Она устраняет только требование выравнивания. Чтение неинициализированных байтов или произвольного битового шаблона в тип с недопустимыми представлениями по-прежнему может быть неопределённым поведением.
Нет. Ссылка Rust сама обязана быть корректно выровнена, даже если её сразу разыменовать и не сохранить. Для невыровненного доступа нужно читать через read_unaligned либо скопировать байты в выровненную область; создание промежуточной ссылки не является безопасным обходом.