Есть ли гарантированный способ утверждать, что данный необработанный указатель выровнен по некоторому значению выравнивания?
Я посмотрел на функцию указателя aligned_offset
, но в документах указано, что для нее допустимо давать ложные отрицательные значения (всегда возвращать usize::MAX
), и что правильность не может зависеть от этого.
Я вообще не хочу возиться с выравниванием, я просто хочу написать утверждение, которое будет паниковать, если указатель не выровнен. Моя мотивация заключается в том, что при использовании некоторых низкоуровневых встроенных функций ЦП передача указателя, не выровненного с какой-либо границей, вызывает ошибку ЦП, и я бы предпочел получить паническое сообщение Rust, указывающее, где находится вызывающая его ошибка, чем SEGFAULT.
Пример утверждения (неверный согласно документам aligned_offset
):
#[repr(align(64))]
struct A64(u8);
#[repr(align(32))]
struct A32(u8);
#[repr(align(8))]
struct A8(u8);
fn main() {
let a64 = [A64(0)];
let a32 = [A32(0)];
let a8 = [A8(0), A8(0)];
println!("Assert for 64 should pass...");
assert_alignment(&a64);
println!("Assert for 32 should pass...");
assert_alignment(&a32);
println!("Assert for 8, one of the following should fail:");
println!("- full array");
assert_alignment(&a8);
println!("- offset by 8");
assert_alignment(&a8[1..]);
}
fn assert_alignment<T>(a: &[T]) {
let ptr = a.as_ptr();
assert_eq!(ptr.align_offset(32), 0);
}
@JMAA Я сомневаюсь, что это будет так просто, поскольку базовое значение указателя зависит от архитектуры. 1. Этот ответ: stackoverflow.com/a/57970536/4646738 указывает на архитектуру, которая так не работает, 2. Если бы это было так просто, я бы ожидал, что стандартная функция просто сделает это, предостережение «может быть неверным» не было бы нужный.
Справедливое замечание, но нужна ли вам поддержка архитектур, которые не представляют указатели таким образом, чтобы они работали так? Вопрос, который вы связали, предполагает, что единственный способ сделать версию, которая работала бы для каждой мыслимой архитектуры, - это написать код для конкретной архитектуры для каждого особого случая. Или вы просто соглашаетесь с тем, что по крайней мере x86, amd64 и aarch64 (AFAIK) работают так, и этого достаточно.
Я также ожидаю, что стандартная версия функции будет работать должным образом на всех таких архитектурах, поэтому вы можете просто добавить дополнительную проверку, чтобы увидеть, возвращает ли она usize::MAX
и терпеть неудачу, если это так, если вы параноик. Но на самом деле, если вы не ориентируетесь на 8086 и одновременно не смотрите на> 16-битное выравнивание, это, вероятно, не проблема.
@JMAA Я стараюсь быть архитектурно-независимым, насколько это возможно, для моего текущего использования. Хотя, даже если я решу пойти с половинчатым решением, вопрос, можно ли это сделать в общих чертах, все еще не дает мне покоя. Вы могу требуете определенного выравнивания от распределителя памяти, поэтому интуитивно (для меня) должен быть какой-то способ попросить распределитель сообщить вам выравнивание.
Я понимаю, но в определенный момент вы должны сократить свои потери. Если вы не ориентируетесь на процессоры до 32-битной версии или особенно странные микроконтроллеры или DSP, то буквально нет причин для дальнейшего беспокойства. Я очень сомневаюсь, что есть даже целевые опоры для ржавчины, где это не сработает.
Просто чтобы удовлетворить свои собственные неврозы, я пошел и проверил источникptr::align_offset
.
Существует много тщательной работы с крайними случаями (например, const
-вычисление всегда возвращает usize::MAX
, аналогично для указателя на тип нулевого размера, и паникует, если alignment
не является степенью числа 2). Но суть реализации для ваших целей — это здесь: требуется (ptr as usize) % alignment == 0
, чтобы проверить, выровнено ли оно.
Edit: This PR is adding a
ptr::is_aligned_to
function, which is much more readable and also safer and better reviewed than simply(ptr as usize) % alginment == 0
(though the core of it is still that logic).
Затем возникает еще одна сложность для вычисления точного смещения (что может быть невозможно), но это не относится к этому вопросу.
Поэтому:
assert_eq!(ptr.align_offset(alignment), 0);
должно быть много для вашего утверждения.
Между прочим, это доказывает, что текущая стандартная библиотека ржавчины не может ориентироваться на что-либо, что не представляет указатели в виде простых числовых адресов, иначе эта функция не работала бы. В маловероятной ситуации, когда стандартная библиотека ржавчины портирована на Intel 8086 или какой-то странный DSP, который не представляет указатели ожидаемым образом, эту функцию придется изменить. Но действительно, вас так волнует эта гипотетика?
И это объяснение, я думаю. Я предполагаю, что в такой эзотерической архитектуре не обязательно будет разумная реализация, она просто по умолчанию будет всегда возвращать usize::MAX
и по-прежнему будет допустимой реализацией Rust для этой архитектуры.
Да, я полагаю, нет никакой гарантии, что is_aligned
вообще возможно на каждой архитектуре. Лучше просто позволить разработчикам std lib иметь дело с любым кодом, специфичным для архитектуры, который может потребоваться.
Ожидается PR для is_aligned()
и is_aligned_to()
: github.com/rust-lang/rust/pull/95643.
Сам по себе тот факт, что std не реализует что-то правильно для какой-то платформы, не означает, что Rust полностью не поддерживает эту платформу, а только то, что на уровне std он не поддерживается (по крайней мере, полностью). В этом случае также есть отличие от C: стандарт C99 гласит, что (§6.3.2.3 Указатели) «любой тип указателя может быть преобразован в целочисленный тип. За исключением ранее указанного, результат определяется реализацией. Если результат не может быть представлен в целочисленный тип, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа."...
... в то время как ссылка на Rust гласит, что (doc.rust-lang.org/stable/reference/expressions/…) «Приведение из необработанного указателя к целому числу производит машинный адрес памяти, на которую ссылаются. Если целочисленный тип меньше, чем тип указателя, адрес может быть усечен; использование usize позволяет избежать этого». Таким образом, стандартная библиотека верна, потому что для этого требуется адрес машины - я прочитал это как фактический адрес после сегментации.
Это определение также совместимо с 8086 (хотя приведения потребуют больше операций для настройки адреса). Хотя это может быть не то, что вы ожидаете: для соответствия каждому адресу потребуется usize
32 бита. Это также необходимо для doc.rust-lang.org/stable/reference/…: «usize
и isize
имеют размер, достаточный для размещения всех адресов на целевой платформе». Но хорошо известно, что Rust плохо поддерживает сегментированные архитектуры, и некоторые недавние разработки (строгое происхождение) могут исправить эту ситуацию. Так что эти определения могут меняться.
Я не эксперт по выравниванию, но не могли бы вы просто привести указатель к
usize
и взять модуль желаемого выравнивания байтов? Что-то вроде(ptr as usize) % alignment == 0
?