函数或模块范围内的静态变量的初始化

YLy*_*Lyu 8 rust

示例代码:

use std::sync::atomic::{AtomicU32, Ordering};

#[derive(Debug)]
struct Token(u32);

impl Token {
    fn new() -> Self {
        static COUNTER: AtomicU32 = AtomicU32::new(1);
        let inner = COUNTER.fetch_add(1, Ordering::Relaxed);
        Token(inner)
    }
}

fn main() {
    let t1 = Token::new();
    let t2 = Token::new();
    let t3 = Token::new();
    println!("{:?}\n{:?}\n{:?}", t1, t2, t3);
}
Run Code Online (Sandbox Code Playgroud)

当我运行上面显示的代码片段时,它会打印:

Token(1)
Token(2)
Token(3)
Run Code Online (Sandbox Code Playgroud)

我在 Rust 参考中发现静态项的初始化是在编译时评估的。

我想知道当程序执行到初始化变量的行时,运行时到底发生了什么COUNTER。编译后的代码是什么样子的,以便它可以忽略初始化?

Fra*_*gné 11

static无论变量是在模块级别还是在函数级别定义,它们的处理方式都是相同的。唯一的区别是名称解析的范围。

许多可执行文件格式(包括ELFPE)在各个部分中构造程序。通常,函数代码位于一个部分中,可变全局变量位于另一部分中,常量(包括字符串文字)位于另一部分中。当操作系统将程序加载到内存中时,这些部分将被映射到具有不同内存保护选项(例如,常量不可写)的内存中,如可执行文件中所指定的那样。

当文档说static项目在编译时初始化时,这意味着编译器在编译时确定该项目的初始值,然后将该值写入已编译的二进制文件中的适当部分。当你的程序运行时,在你的程序有机会运行一条指令之前,该值就已经在内存中了。

编译器能够计算表达式,AtomicU32::new(1)因为AtomicU32::new它被定义为const fn. 添加const到函数定义意味着它可以在编译时计算的表达式中使用,但const fns 的功能比常规函数要有限得多。但在这种情况下AtomicU32::new,函数需要做的就是初始化AtomicU32结构,它只是u32.