未定义的行为

如果 Rust 代码表现出以下列表中的行为,则代码不正确。包括 unsafe 块和 unsafe 函数中的代码。 unsafe 仅表示避免未定义行为产生是由程序员负责,但无法保证不会导致未定义行为。

在编写 unsafe 代码时,程序员的责任是确保与 unsafe 代码交互的安全代码不会触发这些行为。 如果安全代码始终不会触发 unsafe 代码的未定义行为则称为 健壮的 , 反之,如果安全代码可以触发 unsafe 代码的未定义行为,就是不健壮的

警告: 以下是一个非穷尽的列表。 Rust 中不允许哪些操作和不允许的行为没有正式的模型,因此可能还有更多被视为非安全的行为。 以下列表仅列出了已知的确定的未定义行为。请在编写非安全代码之前阅读 Rustonomicon

  • 数据竞争。

  • 在使用 解引用表达式 (*expr) 对一个 悬垂指针 或非对齐指针进行求值时,甚至在 位置表达式上下文 中 (例如 addr_of!(*expr)) 。

  • 违反 指针别名规则Box<T> , &mut T&T 遵循 LLVM 的有界 无别名 模型,除非 &T 包含 UnsafeCell<U>。 引用和 box 在其存在期间不能是 悬垂 的。确切的存活期未被指定,但存在某些限制:

    • 对于引用,存活期上限由借用检查器分配的语法生命周期确定;它的生存期不能比那个生命周期更长。
    • 每次引用或 box 传递给或从函数返回时,都被认为是活动的。
    • 当引用 (但不是 Box! ) 传递给函数时,它的生存期至少与该函数调用相同,除非 &T 包含一个 UnsafeCell<U>

    以下内容也适用于这些类型的值作为复合类型的 (嵌套) 字段传递,但不适用于指针间接引用的情况。

  • 修改不可变数据。 const 中的所有数据都是不可变的。此外,通过共享引用访问的数据或由不可变绑定拥有的数据也是不可变的,除非该数据包含在 UnsafeCell<U> 中。

  • 通过编译器内部函数调用产生未定义行为。

  • 执行使用当前平台不支持的平台特性编译的代码 (请参阅 target_feature) , 除非 该平台明确记录此操作是安全的。

  • 使用错误的调用 ABI 调用函数或从错误的取消 ABI 函数中回溯。

  • 在私有字段和局部变量中生成无效值。 "生成" 值是指将值分配给或从地址读取值、将值传递给函数/原始操作或从函数/原始操作返回值。 以下值是无效的 (在其各自的类型中) :

    • bool 中除 false (0) 或 true (1) 之外的值。

    • 枚举中的判别值不在类型定义中。

    • 空的 fn 指针。

    • char 中的值是代理或大于 char::MAX

    • ! (所有值对于此类型都是无效的) 。

    • 未初始化的内存 中获取的整数 ( i*/u* ) 、浮点数 ( f* ) 或裸指针,或在 str 中使用未初始化的内存。

    • 悬垂引用或 Box<T> ,非对齐或指向无效值。

    • 宽指针、 Box<T> 或裸指针中的无效元数据:

      • 如果 dyn Trait 的元数据不是指向与指针或引用指向的实际动态特性相匹配的 Trait 的虚表的指针,则该元数据是无效的。
      • 如果长度不是有效的 usize (即不能从未初始化的内存中读取) ,则切片元数据无效。
    • 自定义无效值类型的无效值。在标准库中,这会影响 NonNull<T>NonZero*

      注意: rustc 使用未稳定的 rustc_layout_scalar_valid_range_* 属性来实现此功能。

  • 不正确使用内联汇编。有关更多详细信息,请参阅使用内联汇编编写代码时要遵循的 规则

注意: 对于任何具有受限制的有效值集的类型,未初始化的内存也是隐式无效的。换句话说,读取未初始化的内存仅在 union 内部和在类型的字段/元素之间 "填充" (间隙)中是允许的。

注意: 未定义行为影响整个程序。例如,在 C 中调用表现出未定义行为的函数意味着你的整个程序包含未定义行为,这也可能影响 Rust 代码。反之,在 Rust 中发生未定义行为也会对调用其他语言的 FFI 调用执行的代码产生不利影响。

悬垂指针

如果引用/指针为 null ,或者它所指向的字节不全属于同一个处于活动状态的分配 (因此特别注意它们必须全部属于 某个 分配) ,则该引用/指针是 "悬垂" 的。 它所指向的字节跨度由指针值和指向类型的大小确定 (使用 size_of_val ) 。

如果大小为 0 ,则指针必须指向处于活动状态分配的内部 (包括指向分配的最后一个字节的后面) ,或者直接从非零整数字面值构建而来。

请注意,动态大小的类型 (例如切片和字符串) 指向其整个范围,因而表示的长度元数据不要太大。 特别指出的是, Rust 值的动态大小不能超过 isize::MAX (使用 size_of_val 确定) 。