Атрибут #[repr(packed)]
Атрибут #[repr(packed)] применяется к структурам и объединениям
и указывает компилятору упаковывать поля без добавления
дополнительных байтов выравнивания. По умолчанию Rust выравнивает
поля структуры так, чтобы каждое поле находилось по адресу,
кратному его выравниванию. Это ускоряет доступ к памяти,
но увеличивает размер типа. Атрибут #[repr(packed)] убирает
это выравнивание, делая структуру максимально компактной.
Работа с полями упакованной структуры является небезопасной,
потому что поле может находиться по невыровненному адресу.
Чтение такого поля по ссылке или получение ссылки на него
может привести к неопределённому поведению. Именно поэтому
использование #[repr(packed)] относится к разделу unsafe:
для доступа к полям требуется либо копирование значения,
либо использование небезопасных операций чтения и записи
по невыровненному указателю.
Синтаксис
#[repr(packed)]
struct StructName
{
field1: Type1,
field2: Type2,
}
Также можно указать конкретное выравнивание:
#[repr(packed(2))]
struct StructName
{
field1: Type1,
field2: Type2,
}
Пример
Давайте сравним размер обычной структуры и структуры
с атрибутом #[repr(packed)]. Обычная структура с полями
u8 и u32 занимает 8 байт из-за выравнивания,
а упакованная - только 5 байт:
use std::mem::size_of;
struct Normal
{
a: u8,
b: u32,
}
#[repr(packed)]
struct Packed
{
a: u8,
b: u32,
}
fn main()
{
println!("Normal: {}", size_of::<Normal>());
println!("Packed: {}", size_of::<Packed>());
}
Результат выполнения кода:
Normal: 8
Packed: 5
Пример
Давайте прочитаем поле упакованной структуры. Прямое взятие ссылки на поле запрещено компилятором, но можно скопировать значение поля в локальную переменную, что безопасно:
#[repr(packed)]
struct Packed
{
a: u8,
b: u32,
}
fn main()
{
let p = Packed { a: 1, b: 2 };
let a = p.a;
let b = p.b;
println!("a = {}, b = {}", a, b);
}
Результат выполнения кода:
a = 1, b = 2
Пример
Давайте получим ссылку на поле упакованной структуры через небезопасный блок. Это возможно, но требует внимательности, поскольку ссылка может указывать на невыровненный адрес:
#[repr(packed)]
struct Packed
{
a: u8,
b: u32,
}
fn main()
{
let p = Packed { a: 1, b: 2 };
let a_ref = unsafe { &p.a };
println!("a = {}", *a_ref);
}
Результат выполнения кода:
a = 1
Пример
Давайте запишем новое значение в поле упакованной структуры через небезопасный блок. Для этого используется запись по невыровненному указателю:
#[repr(packed)]
struct Packed
{
a: u8,
b: u32,
}
fn main()
{
let mut p = Packed { a: 1, b: 2 };
unsafe {
let ptr = &mut p.b as *mut u32;
ptr.write_unaligned(42);
}
let b = p.b;
println!("b = {}", b);
}
Результат выполнения кода:
b = 42
Смотрите также
-
атрибут
repr(C),
который задаёт C-совместимое представление структуры -
атрибут
repr(transparent),
который делает структуру прозрачной для ABI -
функция
read_unaligned,
которая читает значение по невыровненному указателю -
функция
write_unaligned,
которая записывает значение по невыровненному указателю