整书完结,增加描述文档
This commit is contained in:
7
22.Unsafe Operations/Cargo.lock
generated
Normal file
7
22.Unsafe Operations/Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "unsafe_operations"
|
||||
version = "0.1.0"
|
||||
8
22.Unsafe Operations/Cargo.toml
Normal file
8
22.Unsafe Operations/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "unsafe_operations"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
453
22.Unsafe Operations/src/main.rs
Normal file
453
22.Unsafe Operations/src/main.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
//!
|
||||
//! 不安全操作(Unsafe Operations)
|
||||
//!
|
||||
//! 这一章是从 [`官方文档`](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html),“应该尽量减少
|
||||
//! 代码库中不安全代码的数量”,基于这个理念,我们开始这一章。
|
||||
//!
|
||||
//! 不安全声明是为了绕过 Rust 编译器的安全检查,使用不安全代码是为了解决下面这些问题。
|
||||
//!
|
||||
//! - 直接使用原始指针
|
||||
//! - 使用 `unsafe` 关键字来调用一些方法(比如 `FFI`,或一些不安全的操作)
|
||||
//! - 修改静态可变的一些变量(比如使用 `static` 关键字定义的常量)
|
||||
//! - 实现一些不安全的特性
|
||||
//!
|
||||
|
||||
///
|
||||
/// 原始指针使用 `*` 操作符来访问,和 `&T` 中解引用的行为类似,
|
||||
/// 但是引用始终都是安全的并且保证引用的数据永远都是有效的,并且会被编译使用借用规则来校验。
|
||||
///
|
||||
/// 解引用一个原始指针只能使用 `unsafe` 关键字内部使用。
|
||||
///
|
||||
fn raw_pointers() {
|
||||
let raw_p: *const u32 = &10;
|
||||
unsafe {
|
||||
assert!(*raw_p == 10);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 有些不安全的函数只能使用 `unsafe` 块来包裹才能使用,使用这些不安全函数
|
||||
/// 只能由使用者来保证函数的正常运行,编译器不会进行安全检查。
|
||||
/// 下面这个例子是 `std::slice::from_raw_parts` 它可以通过原始指针和元素长度来创建
|
||||
/// 一个切片。
|
||||
///
|
||||
fn calling_unsafe_functions() {
|
||||
use std::slice;
|
||||
|
||||
let some_vector = vec![1, 2, 3, 4];
|
||||
|
||||
let pointer = some_vector.as_ptr();
|
||||
let length = some_vector.len();
|
||||
|
||||
unsafe {
|
||||
// 这里必须要保证传递给函数的指针和长度以及数据类型是正确的,
|
||||
// 因为编译器不会检查这些参数是否是有效的,如果数据无效则会导致未知的行为。
|
||||
let my_slice: &[i32] = slice::from_raw_parts(pointer, length);
|
||||
assert_eq!(some_vector.as_slice(), my_slice);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 内联汇编(Inline assembly)
|
||||
///
|
||||
/// Rust 提供了 `asm!` 宏可以直接在代码中嵌入汇编代码,这个特性在开发嵌入式的时候非常有用,
|
||||
/// 这些通过 `asm!` 宏书写的汇编代码会被编译器直接嵌入到最终的二进制文件中。
|
||||
/// 通常情况下这个特性不会被使用,但是有些性能敏感的操作无法通过常规代码实现的场景中非常有用。
|
||||
///
|
||||
/// 使用底层机器语言或者内核编程会使用这个特性。
|
||||
///
|
||||
/// 注意:下面这个实例仅仅是针对 `x86/x86-64` 架构的汇编,其他的架构可以通过自己查阅硬件手册即可。
|
||||
///
|
||||
/// 内联汇编当前支持的架构有
|
||||
///
|
||||
/// - `x86/x86-64`
|
||||
/// - `ARM`
|
||||
/// - `AArch64`
|
||||
/// - `RISC-v`
|
||||
///
|
||||
fn inline_assembly() {
|
||||
use std::arch::asm;
|
||||
|
||||
// 基础示例
|
||||
{
|
||||
unsafe {
|
||||
// NOP(no operation) 汇编指令是 x86 架构下的一个指令,该指令占一个字节,字节码是 `0x90`,含义为当前指令没有任何操作。
|
||||
asm!("nop");
|
||||
}
|
||||
}
|
||||
|
||||
// 输入和输出
|
||||
{
|
||||
let x: u64;
|
||||
unsafe {
|
||||
// mov 这个是 x86 架构下的一个移动数据的指令,使用两个参数,一个是目标寄存器,一个是向寄存器中写入的数据。
|
||||
// 这里实际上的操作是 `x = 5` ,可以看到这里写的内容是字符串,实际上该字符串是一个模版字符串,它和普通的模版字符串拥有相同的使用规则,
|
||||
// 这里有一点区别的是模版字符串后面跟随的参数,这里首先要先定义我们使用的变量是输入还是输出,
|
||||
// `out(reg) x` 代表的是输出,还有就是我们需要指定需要使用哪个寄存器,在这个示例中
|
||||
// 我们使用了一个任意可用的寄存器也就是 `reg` ,编译器会自动选择一个在当前代码的上下文中可以被使用的寄存器,
|
||||
// 然后会自动把 `reg` 替换成一个可用的具体寄存器。
|
||||
|
||||
asm!("mov {}, 5", out(reg) x);
|
||||
}
|
||||
|
||||
assert_eq!(x, 5);
|
||||
|
||||
let i: u64 = 3;
|
||||
let o: u64;
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {0}, {1}", // 汇编的赋值操作符,把数据 i 和 o 读取到寄存器中,并把 o 赋值为 i,也就是说 `o = i`,
|
||||
"add {0}, 5", // 汇编加法,再把 o 的数据 +5,也就是 `o += 5`,
|
||||
out(reg) o,
|
||||
in(reg) i,
|
||||
)
|
||||
}
|
||||
assert_eq!(o, 8);
|
||||
|
||||
let mut x: u64 = 3;
|
||||
|
||||
unsafe {
|
||||
// 使用 `inout` 关键字来定义 `x` 既是输入也是输出,
|
||||
// 然后调用汇编加法对变量进行加法操作,所有的操作都会发生在同一个寄存器上。
|
||||
asm!("add {0}, 5", inout(reg) x);
|
||||
}
|
||||
|
||||
assert_eq!(x, 8);
|
||||
|
||||
let x: u64 = 3;
|
||||
let y: u64;
|
||||
unsafe {
|
||||
// 使用 `inout(reg) x => y` 来简化输入输出的变量定义,
|
||||
// `x` 是输入 `y` 是输出
|
||||
// 这里也会保证操作只发生在同一个寄存器上。
|
||||
asm!("add {0}, 5", inout(reg) x => y);
|
||||
}
|
||||
assert_eq!(y, 8);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 延迟输出操作符 (独占寄存器标记)
|
||||
///
|
||||
/// `Rust` 会假设对于变量的写入再汇编代码的任意位置都可能发生,那么对于给该变量
|
||||
/// 分配的寄存器就不能被其他变量占用,而 `Debug` 情况下默认是不会进行代码优化的,
|
||||
/// 所以在 Debug 的时候可能代码运行的结果没有问题,但是使用 `Release` 模式输出的结果
|
||||
/// 就是错误的,因为 `Release` 模式下 `Rust` 编译器会尽可能的少申请寄存器使用,
|
||||
/// 可能好几个变量使用的是同一个寄存器,那么在这种情况下可能会造成中间状态的错误,最终导致结果错误!
|
||||
///
|
||||
/// 为了避免这种情况的发生 `Rust` 提供了 `lateout` 用来标记该变量需要一个独占的寄存器,
|
||||
/// 该标记还有一个 `inlateout` 的变种,这个变种标记这个变量既是输入也是输出。
|
||||
///
|
||||
///
|
||||
fn late_output_operands() {
|
||||
{
|
||||
use std::arch::asm;
|
||||
|
||||
let mut a: u64 = 4;
|
||||
let b: u64 = 4;
|
||||
let c: u64 = 4;
|
||||
unsafe {
|
||||
asm!(
|
||||
"add {0}, {1}",
|
||||
"add {0}, {2}",
|
||||
inout(reg) a, // 这里在 `Release` 模式下可能会出错,因为 三个变量都是 `in` 而且值是一样的,
|
||||
in(reg) b, // 所以三个变量在 `Release` 模式下会被分配相同的寄存器,这样就会导致中间结果错误,最终导致结果错误。
|
||||
in(reg) c,
|
||||
);
|
||||
}
|
||||
assert_eq!(a, 12);
|
||||
}
|
||||
{
|
||||
use std::arch::asm;
|
||||
|
||||
let mut a: u64 = 4;
|
||||
let b: u64 = 4;
|
||||
unsafe {
|
||||
// 这里使用 `inlateout` 来标记 `a` 的寄存器是独占的,这样的话 `a` 和 `b` 就各自拥有了不同的寄存器。
|
||||
asm!("add {0}, {1}", inlateout(reg) a, in(reg) b);
|
||||
}
|
||||
assert_eq!(a, 8);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 手动指定寄存器
|
||||
///
|
||||
/// 有些特殊的指令要求指定的操作数要在指定的寄存器下才生效,
|
||||
/// 因此 Rust 内联汇编提供了特殊的标记来标识使用的是哪一个寄存器,
|
||||
///
|
||||
/// `reg` 是一个通用的寄存器标志它可以自动选择对应平台当前可用的寄存器,
|
||||
/// 而明确指定寄存器是一个高度和目标平台相关的你使用的寄存器必须是目标平台提供的寄存器,
|
||||
/// 比如 `x86` 平台 通用的寄存器有 `eax`,`ebx`,`ecx`,`edx`,`ebp`,`esi`,`edi`
|
||||
/// 还有一些特殊的寄存器没有名字可以通过寄存器地址来访问。
|
||||
///
|
||||
/// 注意:不像是其他的操作符,手动指定寄存器
|
||||
///
|
||||
fn explicit_register_operands() {
|
||||
use std::arch::asm;
|
||||
|
||||
let cmd = 0xd1; // 这个指令是控制输出端口的写入动作
|
||||
unsafe {
|
||||
// 这里直接使用特殊的寄存器地址 `0x64` 来操作寄存器,这个寄存器是控制寄存器
|
||||
// 这个特殊的寄存器只能通过 `eax` 这个寄存器来操作,所以这里手动指定使用的寄存器是 `eax`
|
||||
asm!("out 0x64, eax", in("eax") cmd);
|
||||
}
|
||||
|
||||
fn asm_add(a: u64, b: u64) -> u128 {
|
||||
let lo: u64;
|
||||
let hi: u64;
|
||||
|
||||
// `x86` 的通用寄存器 `eax` `ebx` `ecx` `edx` 等等都是 `32` 位寄存器
|
||||
// `rax` `rbx` `rcx` `rdx` 等等是 `64` 位寄存器
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
// x86 的乘法指令只有一个操作数,这个操作数接收一个数字,
|
||||
// 然后第二个数字必须存放在 `rax` 寄存器中,计算的最终结果会被放在 `rax:rdx` 中,把两个寄存器当成一个寄存器使用。
|
||||
"mul {}",
|
||||
in(reg) a,
|
||||
inlateout("rax") b => lo,
|
||||
lateout("rdx") hi
|
||||
);
|
||||
}
|
||||
|
||||
// 这里进行强制类型转换然后通过位运算合并计算结果成一个最终结果。
|
||||
((hi as u128) << 64) + lo as u128
|
||||
}
|
||||
|
||||
let test = asm_add(100, 200);
|
||||
assert_eq!(test, 300);
|
||||
}
|
||||
|
||||
///
|
||||
/// 在很多场景下内联汇编只用于修改一些状态并不需要输出数据,但是因为操作可能会导致寄存器上下文丢失,
|
||||
/// 通常都是一些只需要汇编内部自处理的逻辑,或者操作了一些会破坏当前寄存器的汇编代码。
|
||||
///
|
||||
/// 这样的代码被称为 `clobbered(破坏性代码)`,这里就需要明确的告诉编译器,我们的内联汇编可能
|
||||
/// 会丢失汇编块周围的寄存器上下文,需要先保存这些寄存器的值,然后再执行汇编代码,最后恢复寄存器的值
|
||||
///
|
||||
fn clobbered_registers() {
|
||||
use std::arch::asm;
|
||||
|
||||
// 有三条数据,每条数据四个字节
|
||||
let mut name_buf = [0_u8; 12];
|
||||
|
||||
// `ascii` 格式的字符串会被按顺序保存在 `ebx` `edx` `ecx` 寄存器中
|
||||
// 因为 `ebx` 是一个有特殊作用的寄存器,所以写汇编代码的时候需要先保存 `ebx` 这个寄存器的数值,使用完成后再恢复回去。
|
||||
// 在 `64位` 架构的 `64位` 系统上不允许 `push/pop` 指令直接使用 `32位` 的寄存器,所以这里只能使用扩展后的 `rbx` 寄存器,(`rbx` 是 `ebx` 的 `64位` 扩展)
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
"push rbx", // 这里使用 `rbx` 来代替 `ebx`
|
||||
"cpuid", // 特殊指令 读取 `cpuid` 读取的结果会依次存放到 `ebx` `edx` `ecx`
|
||||
"mov [rdi], ebx",
|
||||
"mov [rdi + 4], edx",
|
||||
"mov [rdi + 8], ecx",
|
||||
"pop rbx", // 恢复寄存器
|
||||
// 使用具名的寄存器 `rdi` 来保存数组的原始指针。
|
||||
in("rdi") name_buf.as_mut_ptr(),
|
||||
// 通过 `eax` 寄存器明确指定 `cpuid` 指令的参数为 `0` 获取 `cpu` 的厂商信息,并且告诉编译器这个寄存器的数据会被覆盖掉
|
||||
inout("eax") 0 => _,
|
||||
// 这里的两个寄存器的数据也会被覆盖掉
|
||||
out("ecx") _,
|
||||
out("edx") _,
|
||||
);
|
||||
}
|
||||
|
||||
let name = core::str::from_utf8(&name_buf).unwrap();
|
||||
println!("CPU Manufacturer ID: {}", name);
|
||||
|
||||
{
|
||||
// 使用位移和加法实现 `x * 6`;
|
||||
let mut x: u64 = 4;
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {tmp}, {x}", // 复制数据到 `tmp` 寄存器中
|
||||
"shl {tmp}, 1", // 使用 `shl` 对 `tmp` 左移 `1` 位 等于 `tmp * 2`
|
||||
"shl {x}, 2", // 使用 `shl` 对 `x` 左移 `2` 位 等于 `x * 4`
|
||||
"add {x}, {tmp}", // 对 `x` 和 `tmp` 结果相加并把结果放在 `x` 中
|
||||
x = inout(reg) x,
|
||||
tmp = out(reg) _,
|
||||
);
|
||||
}
|
||||
assert_eq!(x, 4 * 6);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 默认情况下内联汇编会认为所有没被标记为输出的寄存器的值都有汇编代码来维护其内容,
|
||||
/// 换句话来说,就是内联汇编中没有被标记为输出的寄存器的内容都会被上下文保留,
|
||||
/// 也就是说,汇编代码中硬编码了一些其他的寄存器这些寄存器的值被你手动修改过了,
|
||||
/// 但是这个寄存器并没有被标记为输出寄存器,那么这个时候当汇编代码被执行完以后,
|
||||
/// 这个寄存器的值就是你在汇编代码中最后修改的值,这样的话可能会造成其他的运行错误,
|
||||
/// 因为你修改了一个寄存器,这个寄存器可能保存了原来的代码上下文环境。
|
||||
///
|
||||
/// 因此可以使用 `clobber_abi("C")` 关键字来指定所有的非改调用约定的寄存器的值
|
||||
/// 在汇编运行之前会自动保留,等汇编执行完成后会自动恢复这些寄存器的值,以保证代码的正确性。
|
||||
///
|
||||
fn symbol_operands_and_abi_clobbers() {
|
||||
use std::arch::asm;
|
||||
|
||||
extern "C" fn foo(arg: i32) -> i32 {
|
||||
println!("arg = {}", arg);
|
||||
arg * 2
|
||||
}
|
||||
|
||||
fn call_foo(arg: i32) -> i32 {
|
||||
unsafe {
|
||||
let result;
|
||||
asm!(
|
||||
"call {}",
|
||||
// 传入函数指针
|
||||
in(reg) foo,
|
||||
// `64位` 系统下默认使用 `rdi` 寄存器的值当做第一个参数。
|
||||
in("rdi") arg,
|
||||
// 返回值保存在 `rax` 寄存器
|
||||
out("rax") result,
|
||||
// 让所有非 `C` 调用约定下的寄存器全都要保存快照,汇编结束后自动恢复。
|
||||
clobber_abi("C"),
|
||||
);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
let x = call_foo(100);
|
||||
println!("{}", x);
|
||||
}
|
||||
|
||||
///
|
||||
/// 有些时候需要对单个寄存器进行拆分使用,这时候可以通过模版字符串来进行精细化控制,
|
||||
/// 在汇编语言中一个寄存器可能会被拆分成多个小的子集,比如说 `eax` 是 rax 的低 `32位`,
|
||||
/// `ax` 是 `eax` 的低 `16位`,`ah` 是 `ax` 的高 `8位`,`al` 是 `ax` 的低 `8位`,
|
||||
/// 等等以此类推。
|
||||
///
|
||||
/// 默认情况下编译器都会优先使用完整的寄存器名称 比如说 64位系统下默认选择 `rax`,在 `32位` 系统下
|
||||
/// 默认选择 `eax` 等等。
|
||||
///
|
||||
/// 这些默认行为可以通过模版字符串中指定操作数来修改。
|
||||
///
|
||||
fn register_template_modifiers() {
|
||||
use std::arch::asm;
|
||||
|
||||
let mut x: u16 = 0xab;
|
||||
|
||||
unsafe {
|
||||
asm!(
|
||||
// 这里最终会被展开成 `move ah, al`。
|
||||
"mov {0:h}, {0:l}",
|
||||
// 这里选择告诉编译器可以选择 `eax` `ebx` `ecx` `edx` 的任意一个寄存器来使用,
|
||||
// 具体选择了哪一个由编译器来决定,所以这里实际上是把 `x` 的数值放到了 `ax` `bx` `cx` `dx`,
|
||||
// 的任意一个寄存器中,然后模版字符串中通过 `0:l` 来访问低八位,通过 `0:h` 来访问高八位。
|
||||
inout(reg_abcd) x
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(x, 0xabab);
|
||||
}
|
||||
|
||||
///
|
||||
/// 有些情况下汇编指令需要操作的是一个间接的内存地址,
|
||||
/// 你需要使用内存地址访问的特殊语法(这个和目标平台相关,需要查阅不同平台的手册)来进行内存地址的访问。
|
||||
///
|
||||
fn memory_address_operands() {
|
||||
use std::arch::asm;
|
||||
let control: u16 = 0;
|
||||
unsafe {
|
||||
// 在 `x86` 体系中 使用 `[]` 来访问内存地址
|
||||
asm!("fldcw [{}]", in(reg) &control, options(nostack));
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// 汇编中的标签重用可能会导致编译或者连接错误或者其他的一些奇怪的行为,
|
||||
/// 在下面这些情况下都会可能会出现标签的重用。
|
||||
///
|
||||
/// - 明确的标签重用,同一个标签在一个 `asm!` 块中出现了多次,或者一个标签在多个 `asm!` 快中出现多次。
|
||||
/// - 内联宏(`#[inline]`)产生的隐式标签,编译器允许同一个 `asm!` 被内联到多个地方中,这个时候就会出现标签的重复使用。
|
||||
/// - 隐式的编译器优化(`LTO`),编译器优化的时候会使用其他包的代码合并到一起再次尝试优化,这时候也会出现标签的重复使用。
|
||||
///
|
||||
/// 所以这里更推荐使用 `GNU` 数字表示的局部标签来定义标签名,这样的话就不会产生因为标签的重定义问题导致的汇编或者链接错误。
|
||||
///
|
||||
/// 另外在 `x86` 架构上当使用默认的 `Intel` 语法时候由于 `LLVM` 的问题,不应该使用只有 `0` 和 `1` 数字组成的标签,比如 `0` `11` `101010`,
|
||||
/// 因为会被解析成一个二进制的数据,可以使用 `options(att_syntax)` 来避免这些歧义,但这个会影响所有的 `asm!` 语法。
|
||||
///
|
||||
fn labels() {
|
||||
use std::arch::asm;
|
||||
|
||||
let mut a = 0;
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {0}, 10",
|
||||
"2:", // 使用数字本地标签语法来定义标签,这里没有使用 `0` 或 `1` 因为 `LLVM` 的问题直接从数字 `2` 开始使用
|
||||
"sub {0}, 1",
|
||||
"cmp {0}, 3",
|
||||
"jle 2f", // 这里使用 `2f` 代表 `forward to 2`,也就是指的下面定义的 `2:` 标签
|
||||
"jmp 2b", // 这里使用 `2b` 代表 `backword to 2`,也就是指的上面定义的 `2:` 标签
|
||||
"2:",
|
||||
"add {0}, 2",
|
||||
out(reg) a
|
||||
);
|
||||
}
|
||||
assert_eq!(a, 5);
|
||||
}
|
||||
|
||||
///
|
||||
/// 默认情况下内联汇编的代码会被认为和 FFI 的方法是一样的,
|
||||
/// 调用这些方法会做一些自定义的转换,可能会进行内存的读写,或者有可观察的一些副作用等等,
|
||||
/// 所以需要为编译器提供尽可能多的一些信息来告诉编译器应该哪里能够进行优化。
|
||||
///
|
||||
/// 明确指定汇编选项可以让编译器能更好的进行代码优化。
|
||||
///
|
||||
/// 更多的选项可以[参考这里](https://doc.rust-lang.org/stable/reference/inline-assembly.html)
|
||||
///
|
||||
fn options() {
|
||||
use std::arch::asm;
|
||||
// 使用前一个代码来举例子
|
||||
{
|
||||
let mut a: u64 = 4;
|
||||
let b: u64 = 4;
|
||||
unsafe {
|
||||
asm!(
|
||||
"add {0}, {1}",
|
||||
inlateout(reg) a, in(reg) b,
|
||||
// options 这个参数必须要放在 `asm!` 块的最后一个参数上,
|
||||
// 参数中比较常见的是下面这三个值
|
||||
// - pure 这个参数表示汇编代码中没有任何副作用,输出只和输入相关联,这样的话编译器可以最大力度的优化代码,如果代码足够简单的话,编译器甚至在保证功能一样的同时可以把这些代码直接优化掉。
|
||||
// - nomem 这个参数表示汇编代码中不需要读写内存,默认情况下编译器会认为汇编代码会读写任意内存地址。(通过(寄存器中保留的)传递的指针,或者全局变量)
|
||||
// - nostack 这个参数表示汇百年代码中没有任何对于栈的操作,这个可以允许编译器使用特定平台对于栈的特殊优化手段来避免出现栈的重新调整。
|
||||
options(pure, nomem, nostack),
|
||||
);
|
||||
}
|
||||
assert_eq!(a, 8);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 操作原始指针
|
||||
raw_pointers();
|
||||
// 调用函数
|
||||
calling_unsafe_functions();
|
||||
|
||||
{
|
||||
// 内联汇编
|
||||
inline_assembly();
|
||||
// 寄存器独占
|
||||
late_output_operands();
|
||||
// 明确指明寄存器 平台相关
|
||||
explicit_register_operands();
|
||||
// 标记寄存器的状态
|
||||
clobbered_registers();
|
||||
// 快捷标记寄存器状态,通过调用约定来标记
|
||||
symbol_operands_and_abi_clobbers();
|
||||
// 通过模版字符串来精细化操作寄存器
|
||||
register_template_modifiers();
|
||||
// 内存指针访问,平台相关
|
||||
memory_address_operands();
|
||||
// 汇编中的命名标签
|
||||
labels();
|
||||
// 汇编的优化配置项
|
||||
options();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user