Устройство памяти в Rust. Стек и куча

Последнее обновление: 14.04.2021

Вся память в Rust делится на два типа: стек (stack) и куча (heap).

Стек устроен по принципу "LIFO" (last in, first out). Каждый новый добавляемый элемент располагается поверх предыдущего. Удаление начиется с верхушки стека. Первым удаляется элемент, который добавлен последним. А те элементы, которые были добавлены в стек первыми, удаляются последними. При этом данные, помещаемые в стек, должны иметь известный фиксированный размер. Таким образом, в стек помещаются данные примитивных типов i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, bool, char, массивы, кортежи.

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

Например:

fn main(){
	
	let x = 10;
	let y = 20;
	let z = 3.4;
	
	println!("x={}  y={}  z={}", x, y, z);
}

В данном случае все три переменных имеют фиксированные размеры: целочисленные переменные x и y представляют тип i32 (4 байта), а переменная z - тип f64 (8 байт). Поскольку числовые данные имеют фиксированный размер, они размещаются в стеке. И в данном случае мы можем представить стек следующим образом:

Stack in Rust

Куча или хип (heap) работает иначе. При размещении данных в куче распределитель памяти (memory allocator) находит в куче кусок памяти достаточного размера, чтобы вместить данные, и возвращает указатель - адрес этого куска в памяти, куда помещены данные.

Указатель имеет фиксированный размер - 8 байт, поэтому помещается в стеке. Однако непосредственные данные размещаются в куче. А для обращения к этим данным сначала идет обращение к указателю в стеке, а через указатель к самим данным, размещенным в куче.

Размещение данных в стеке происходит быстрее, чем размещение в куче. Так как распределителю памяти не надо искать свободное место достаточного размера, а данные всегда располагаются поверх уже имеющихся. В то время как выделение памяти в куче требует большей работы. Кроме того, доступ к данным в куче происходит медленнее, чем в стеке.

Рассмотрим простейший пример:

fn main(){
	
	
	let s1: String = "hello".to_string();
	println!("{}", s1);
	
}

Здесь переменная s1 представляет тип String. Встроенная структура String представляет строку, которые можно динамически изменять. А это значит, что нам точно неизвестно, сколько именно памяти будет занимать переменная этого типа. Поэтому непосредственно данные строки (набор байт) помещаются в кучу (heap).

Чтобы получить объект типа String, у строкового литерала "hello" вызывается метод to_string().

В данном случае стек и кучу мы можем представить визуально следующим образом:

Куча heap in Rust

В стек помещаются те данные переменной s1, которые имеют фиксированный размер. Это размер строки (len) - в данном случае 5 символов, размер буфера для строки (capacity) - в данном случае также 5 символов и указатель - адрес в куче, где собственно размещена строка. Например, это может быть адрес 0x2061aa10770, по которому располагается строка "hello".

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850