Инициализация статической переменной в области действия функции или модуля

#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);
}
 

Когда я запускаю фрагмент кода, показанный выше, он выводит:

 Token(1)
Token(2)
Token(3)
 

Я нашел в ссылке на Rust, что инициализация статических элементов оценивается во время компиляции.

Интересно, что именно происходит во время выполнения, когда программа выполняется до строки, которая инициализирует переменную COUNTER . Как выглядит скомпилированный код, чтобы он мог пренебречь инициализацией?

Ответ №1:

static переменные обрабатываются одинаково, независимо от того, определены ли они на уровне модуля или на уровне функции. Единственное отличие заключается в области разрешения имен.

Многие форматы файлов для исполняемых файлов, включая ELF и PE, структурируют программы в различные разделы. Обычно код для функций находится в одном разделе, изменяемые глобальные переменные — в другом разделе, а константы (включая строковые литералы) — в еще одном разделе. Когда операционная система загружает программу в память, эти разделы отображаются в память с различными параметрами защиты памяти (например, константы недоступны для записи), как указано в исполняемом файле.

Когда в документации говорится, что static элементы инициализируются во время компиляции, это означает, что компилятор определяет начальное значение этого элемента во время компиляции, а затем записывает это значение в соответствующий раздел скомпилированного двоичного файла. При запуске вашей программы значение уже будет в памяти, прежде чем ваша программа получит возможность выполнить хотя бы одну инструкцию.

Компилятор может вычислить выражение AtomicU32::new(1) , поскольку AtomicU32::new оно определено как const fn . Добавление const к определению функции означает, что ее можно использовать в выражениях, которые вычисляются во время компиляции, но const fn они намного более ограничены, чем обычные функции, в том, что они могут делать. В случае AtomicU32::new though все, что нужно сделать функции, это инициализировать AtomicU32 структуру, которая является просто оболочкой вокруг a u32 .