Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Распространённые предусловия безопасности

  • Псевдонимы и изменяемость
  • Выравнивание
  • Доступ к массиву в границах
  • Инициализация
  • Время жизни ссылок
  • Происхождение указателя (pointer provenance)
  • Корректность значения (validity)
  • Память
This slide and its sub-slides should take about 6 minutes.

Не тратьте много времени на объяснение каждого предусловия: подробности мы разберём в ходе курса. Цель — показать, что их несколько.

«Неполный список, но это несколько основных предусловий безопасности, которые помогут нам задуматься».

  • Корректность значения (validity). Значения должны быть корректными значениями того типа, который они представляют. Ссылки Rust не могут быть null. Создание такой ссылки через unsafe приводит к
  • Выравнивание (alignment). Ссылки на значения должны быть правильно выровнены, что означает, что
  • Псевдонимы (aliasing). Весь код Rust должен соблюдать правила заимствования Rust. Если вы вручную создаёте изменяемые ссылки (&mut T) из указателей, то можете создать только одну
  • Инициализация. Все экземпляры типов Rust должны быть полностью инициализированы. Чтобы создать значение из сырой памяти, нужно убедиться, что мы записали
  • Происхождение указателя (pointer provenance). Происхождение указателя важно. Приведение usize к сырому указателю больше не допускается.
  • Времена жизни. Ссылки не должны переживать то, на что они указывают.

Некоторые условия даже тоньше, чем кажутся на первый взгляд.

Рассмотрим «доступ к массиву в границах». Для нарушения программы не обязательно читать ячейку памяти, то есть разыменовывать указатель. Создание ссылки за границами массива уже нарушает предположения компилятора и приводит к непредсказуемому поведению.

Rust сообщает LLVM использовать его предположение getelementptr inbounds. Это предположение приводит к тому, что последующие проходы оптимизации в компиляторе ведут себя неправильно, поскольку доступ к памяти за границами не может происходить.

Необязательно: откройте песочницу, в которой показан приведённый ниже код. Объясните, что это, по сути, C-функция, записанная синтаксисом Rust, которая берёт элементы из массива. Сгенерируйте LLVM IR кнопкой Show LLVM IR. Выделите getelementptr inbounds i32, ptr %array, i64 %offset.

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[unsafe(no_mangle)]
pub unsafe fn get(array: *const i32, offset: isize) -> i32 {
    unsafe { *array.offset(offset) }
}

Ожидаемый вывод (строка для выделения начинается с `%_3):

define noundef i32 @get(ptr noundef readonly captures(none) %array, i64 noundef %offset) unnamed_addr #0 {
start:
  %_3 = getelementptr inbounds i32, ptr %array, i64 %offset
  %_0 = load i32, ptr %_3, align 4, !noundef !3
  ret i32 %_0
}

Границы: вы верно заметили, что создание указателя за пределами массива (сверх правила «один за концом») — это неопределённое поведение даже без разыменования, из-за предположений LLVM о inbounds.