Управление памятью ржавчины

Java задняя часть Язык программирования Rust
Управление памятью ржавчины

По сравнению с другими языками программирования, самой большой изюминкой Rust является то, что он вводит набор методов статического анализа во время компиляции для определения области действия и жизненного цикла всех объектов, так что, когда объект больше не используется, его можно точно уничтожить. без внесения какой-либо сложности во время выполнения.

В современных языках программирования для памяти, выделенной в куче (которую можно понимать какmallocУправлять ею можно только двумя способами: пользователь отображает вызывающую функцию в коде и освобождает эту часть памяти или вводит механизм автоматической сборки мусора, который автоматически управляется программой во время выполнения.

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

Типы значений и ссылочные типы

Большинство современных языков программирования делят типы на два типа: типы значений и ссылочные типы.

Типы значений, как правило, аналогичны типам в Java.int / byte / boolЭтот тип данных фиксированного размера, размещаемый в стеке. В Rust все такие типы реализуютCopyЭта черта, чтобы пометить ее как тип значения.

Другой - ссылочный тип с переменным размером/переменным размером, например, в Java.String, этот тип данных на самом деле состоит из двух частей в памяти: одна часть находится в куче, содержимое — это ее фактические данные, а другая часть выделена в стеке, а содержимое — это фактически адрес памяти, указывающий на фактические данные на стек.

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

RAII

RAII расшифровывается как Resource Acquisition Is Initialization и является общей парадигмой программирования на C++. RAII также можно использовать для управления памятью, см. следующий код:

class C {
public:
  int *value;

  C() {
    value = new int();
  }

  ~C() {
    delete value;
  }
};

void f() {
  auto c = C();
}

int main() {
  c();
  return 0;
}

В приведенном выше коде конструктор класса C выполняет выделение памяти, а деструктор — восстановление памяти, так что память в куче, соответствующей этому классу (здесьvalue) привязан к жизненному циклу переменной. В конце области действия переменной память в куче также освобождается, поэтому нам не нужно вручную освобождать в коде.Cсерединаvalueполе памяти. В примере, пока функцияf,c.valueбудут автоматически переработаны.

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

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

Тем не менее, RAII также имеет некоторые недостатки, такие какcПрисвоение другой переменной приведет к двойному вызову деструктора класса, а также корректности в сложных ситуациях, таких как многопоточность.

семантика перемещения

задание Руста (=Если целью является тип значения, это эквивалентно копированию содержимого значения в цель, и изменение исходного значения не будет применяться к новому значению. Это то же самое, что и другие распространенные языки программирования. Например:

fn main() {
    let a = 1;
    let mut b = a;
    b += 1;
    println("a: {}, b: {}", a, b);  // 输出为 "a: 1, b: 2",并且此时两个变量都可以被使用。
}

Как насчет выполнения описанной выше операции над ссылочным типом? мы начинаем сStringНапример:

fn main() {
    let a = String::from("hello");
    let b = a;
    println!("{}", a);
}

На этом этапе мы столкнемся с ошибкой компиляции:

error[E0382]: use of moved value: `a`
 --> a.rs:4:20
  |
3 |     let b = a;
  |         - value moved here
4 |     println!("{}", a);
  |                    ^ value used here after move
  |
  = note: move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait

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

Это гарантирует, что у сегмента памяти, выделенного в куче, будет только один владелец. Это решает вышеупомянутую проблему, связанную с тем, что RAII присваивает переменной ссылочного типа другой тип, и память освобождается дважды.

Цитировать

Однако в Rust, хотя семантика перемещения и гарантирует, что у каждого данных ссылочного типа есть уникальный владелец, это также вызывает неудобства при написании кода. Например, мы хотим написать расчетStringфункция длины:

fn get_string_length(the_s: String) -> usize {
    return the_s.len();
}

fn main() {
    let s = String::from("Hello!");
    get_string_length(s);
    println!("{}'s length is {}", s, length);
}

При компиляции получаю ошибку:

error[E0382]: use of moved value: `s`
 --> a.rs:8:35
  |
7 |     let length = get_string_length(s);
  |                                    - value moved here
8 |     println!("{}'s length is {}", s, length);
  |                                   ^ value used here after move
  |
  = note: move occurs because `s` has type `std::string::String`, which does not implement the `Copy` trait

error: aborting due to previous error

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

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

С этой целью Rust представил引用это понятие. Ссылки чем-то похожи на ссылки в C++ и должны иметь только префикс с переменными и типами.&Префикс подойдет. Давайте перепишем приведенный выше код со ссылками:

fn get_string_length(s: &String) -> usize {
    return s.len();
}

fn main() {
    let s = String::from("Hello!");
    let length = get_string_length(&s);
    println!("{}'s length is {}", s, length);
}

Таким образом, код компилируется и работает правильно.

В Rust через ссылки операции, которые ранее требовали перемещения семантики, станут операциями с заимствованием семантики.Жизненный цикл объекта не будет переноситься, а лишь временно «одалживаться» на новое место.

изменяемость ссылок

Если вы изучили Rust, то должны знать, что при объявлении переменной можно добавитьmutпрефикс, указывающий, что переменная может быть изменена.

При объявлении ссылочного типа вы также можете добавитьmutприставка. Это означает, что заимствованная ссылка может быть изменена заемщиком.

Однако стоит отметить, что переменная может предоставлять только изменяемую ссылку, и в настоящее время нельзя предоставлять больше ссылок (включая неизменяемые ссылки). Это ограничение предназначено для предотвращения проблем с согласованностью данных в случае многопоточности.