一、什么是"裸函数"(naked functions)
- 裸函数用 #[unsafe(naked)] 标注,函数体只能是一个 naked_asm! 调用。
- "裸"的含义是:编译器不会为它自动生成常规函数的"前奏/尾声"(prologue/epilogue)代码,也不会帮你处理参数传递或返回值;函数体完全由你写的汇编指令决定。
- 这和普通的 asm! 不同:普通函数里即便用了 asm!,编译器仍会生成函数的栈帧、保存寄存器等;而 naked 函数则完全不加这些"包装"。
二、为什么需要它(和 global_asm! 相比有什么优势)
过去如果你要写"完全由汇编控制的函数",常见的做法是用 global_asm! 直接把一段全局汇编塞进目标文件。这样做有几个痛点:
-
符号/平台指令的样板代码繁琐且平台相关
- 你得自己写 .section/.globl/.type/.size/.p2align 等指令来"声明一个函数",而且不同目标平台(ELF、Mach-O、COFF)写法不同,机械又容易错。
- 裸函数会自动生成这些平台相关的指令,你只写核心汇编逻辑即可。
-
符号名与名称修饰(name mangling)问题
-
用 global_asm! 时你往往把函数名硬编码成裸符号,不参与 Rust 的名称修饰。这会引起:
- 跨平台兼容性差(比如 macOS x86_64 符号前缀有 _,Linux 没有)。
- 需要让符号全局可见,易发生符号冲突。
-
用裸函数时,函数名按 Rust 规则处理、参与名称修饰,减少这些问题。
-
-
泛型支持受限
- global_asm! 定义的函数无法像正常 Rust 函数一样使用(尤其是 const generics 等)。裸函数因为是"正常函数签名 + 特殊宏体",可以更自然地结合一些泛型场景(受限于汇编可接受的操作数种类)。
-
统一的文档与安全注释位置
- 裸函数只有一个定义点,你可以在它的注释里集中写明安全性约束(非常重要)。global_asm! 往往需要额外的 extern 声明和分散文档,更容易过时或不一致。
三、它解决的核心问题与使用场景
裸函数的典型使用场景都是"非常低层"的地方,你需要绝对控制指令序列与 ABI 交互,不希望编译器插手(如保存/恢复寄存器、栈帧布局等)。
常见场景:
-
启动代码、引导程序、上下文切换
- 比如在操作系统内核、引导加载器、裸机(embedded)环境,函数需要用特定寄存器传参/返回,不希望任何多余指令。
-
异常/中断入口、陷入处理(trampoline)
- 必须手动保存/恢复寄存器、切换栈等,常规函数前后序会干扰。
-
编译器内建(compiler-builtins)、特定 ABI 粘合层
- 一些平台或架构有非标准 ABI 的库函数(例如 ARM EABI 的 _*aeabi** 例程),需要"用你自己定义的调用约定"来衔接其他 C 例程或汇编。
-
极致性能和确定性
- 某些关键路径上希望精确到每条指令的控制。
四、为什么是"unsafe"
-
裸函数跳过了编译器的 ABI 与栈帧安全网,意味着:
- 你必须确保你写的汇编与声明的 ABI、函数签名完全一致(参数在哪些寄存器、谁来保存哪些寄存器、如何返回值等)。
- 稍有不当就可能破坏栈、寄存器约定,导致未定义行为或崩溃。
-
因此 #[unsafe(naked)] 明确标示"这是不安全接口",需要配套完整的 SAFETY 注释说明不变量与约定。
五、naked_asm! 是什么
-
它是专门给裸函数用的汇编宏,介于 asm! 与 global_asm! 之间:
- 写在函数体里(像 asm!),但只接受部分操作数类型(像 global_asm!)。
- 编译器将裸函数"下沉"为全局汇编,从而保证函数体"只包含你写的指令",避免 LLVM 等后端偷偷插入额外指令,并且适配非 LLVM 后端。
六、和未来相关的两个扩展(实验中)
-
extern "custom"(feature: abi_custom)
- 现实中很多裸函数并不真遵循 C 或 sysv64 之类标准 ABI,而是"自定义调用约定"。通过 extern "custom" 能把这种事实表达清楚。
- 重要限制:这类函数不能被普通 Rust 调用表达式直接调用(编译器不知道怎么生成正确调用序列),只能通过内联汇编去"跳"过去。
-
cfg_asm(feature: cfg_asm)
- 允许对汇编块内的"某几行"加 #[cfg(...)] 条件编译。便于按目标/特性开关精细裁剪汇编,而不是复制整段汇编。
七、一个简化的示例对比
-
裸函数写法(更简洁、可移植):
- #[unsafe(naked)]
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) -> u64 {
core::arch::naked_asm!(
"lea rax, [rdi + rsi]",
"ret",
);
}
- #[unsafe(naked)]
-
如果用 global_asm!,你得写一堆 .section/.globl/.type/.size 等平台相关指令,还要处理符号名修饰和可见性问题。
八、我该不该用?
-
如果你在写普通应用(Web 服务、CLI、桌面程序),几乎不需要。
-
如果你在写:
- OS 内核/引导、嵌入式启动代码/中断向量、
- 需要完全掌控寄存器与栈的上下文切换、
- 与非标准 ABI 的底层粘合层、
- 或想在极少数路径上精确控制指令序
那么裸函数会显著提升可读性、可维护性与可移植性,同时减少 global_asm! 的样板与坑点。
九、使用时的注意事项
- 必写清晰的 SAFETY 注释:说明所遵循的 ABI、寄存器约定、被破坏/保存的寄存器、栈使用方式、返回值规则等。
- 确保签名、ABI、实现一致。例如 extern "sysv64" 时,x86_64 的参数寄存器是 rdi、rsi、rdx、rcx、r8、r9,返回值在 rax(必要时 rdx)。
- 谨慎处理被调用者保存(callee-saved)与调用者保存(caller-saved)寄存器。
- 在嵌入式/无操作系统环境,留意栈指针初始化、中断屏蔽、内存栅栏等。
一句话总结:
- 裸函数把"完全由汇编控制函数体"的需求变得更安全、更清晰、更可移植,替代过去繁琐且容易出错的 global_asm! 方案,主要服务于内核、嵌入式、编译器内建等极低层场景。如果你不写这类底层代码,基本用不到;但在需要它的地方,这是一个非常重要的可维护性和健壮性提升。Pomelo_刘金.