[Программирование, Rust] С лёгким налётом ржавчины или немного о владении (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Предыдущая заметка получилась не такая, как я задумывал. Но вызвала небольшую дискуссию. Может быть и в этот раз получится подискутировать. Или получится не так. В любом случае хотелось бы продолжить воровать тексты у богатых и переводить их бедным, т.е. делиться с общественностью пусть даже иногда для кого-то очевидными вещами. Поговорим за динамическую память? Отчасти Rust интересен из-за ряда непопулярных решений в дизайне языка, которые позволяют достичь такого же, а иногда и лучшего результата, чем в других языках. Хорошим примером является управление памятью, а именно управление динамической памятью. Управление памятью может осуществляться двумя способами — явно или автоматически.Явное управление памятью поддерживается в языках системного программирования, таких как C и C++. При явном управлении памятью программист обладает большой свободой. Но с большой властью приходит большая ответственность. При необходимости память может быть динамически выделена в куче и должна быть освобождена, когда в ней больше нет необходимости. При этом важное значение имеет время освобождения памяти — слишком раннее освобождение может привести к использованию недействительных ссылок, позднее освобождение приводит к потере памяти. Программист также должен быть осторожен, чтобы не освободить память более одного раза. Если эти требования не выполняются, могут возникнуть следующие ошибки.1. Утечка памятиУтечка памяти (memory leak) происходит в случае, когда память освобождается слишком поздно (или не освобождается вовсе). Ниже приведён пример, в котором память вообще не освобождается. Оператор new выделяет память в куче и переменная p указывает на адрес массива. Память массива должна быть освобождена, когда массив больше не нужен, но в коде примера этого не происходит. В конечном итоге память, конечно, освобождается, так как ОС очищает ресурсы после завершения программы. Проблема становится более очевидной в случае, если код многократно повторяется в долго работающей программе или на устройстве с ограниченной памятью.
#include <iostream>
using namespace std;
int main()
{
cout << "Before allocate" << endl;
int * p = new int[10];
// ... other program logic
cout << "After allocate" << endl;
// p is not deleted
}
2. Недействительный указательНедействительный указатель (dangling pointer) — это ссылка на память, которая была освобождена. В приведенном ниже фрагменте функция returns_pointer() возвращает указатель на локальную переменную c. Так как c является выделенной на стеке локальной переменной, то, в соответствии с идиомой RAII, её память освобождается, когда переменная выходит из области видимости при завершении функции. Поэтому возвращаемый указатель является недействительным и его использование может вызвать сбой. Недействительные указатели также могут приводить к непредсказуемому поведению и создавать лазейки в безопасности. Эти ошибки обычно сложно отлаживать и исправлять.
#include <iostream>
using namespace std;
char *returns_pointer()
{
char c = 'a';
return &c;
}
int main()
{
char *cp = returns_pointer();
cout << "Result: " << *cp << endl;
}
3. Повреждение памяти в следствии двойного освобожденияПодобно недействительным указателям, двойное освобождение (double free) может причинить вред из-за использования недопустимых ссылок. В приведенном ниже примере показано, как это может привести к непредсказуемому результату. Поскольку память для B выделяется вскоре после освобождения памяти того же размера из-под A, переменной B назначается тот же адрес, что ранее назначался переменной A. Вторая попытка освобождения A не имеет последствий, поскольку она фактически освобождает память B. Программа завершается аварийно при освобождении B, так как память уже была освобождена. Отладка такой ошибки может быть сложной.
#include <iostream>
using namespace std;
int main()
{
int *A = new int[10];
cout << "After delete A: " << A << endl;
delete A;
int *B = new int[10];
delete A;
cout << "After second delete A: " << A << endl;
delete B;
cout << "After delete B: " << B << endl;
}
Обнаружить подобные ошибки может быть очень трудно даже при должной осмотрительности, обзорах кода и бесчисленных тестах. Это приводит к необходимости автоматического управления памятью.Автоматическое управление памятью позволяет абстрагироваться при работе с памятью, предоставляя программисту возможность сосредоточиться на логике приложения. В большинстве языков программирования это достигается при помощи сборщика мусора. Во время своей работы сборщик мусора приостанавливает выполнение программы для очистки объектов, которые больше не используются. Это устраняет ошибки, продемонстрированные выше, но ценой накладных расходов. Наиболее значительными затратами является производительность, так как выполнение программы периодически приостанавливается. Более того, очистка не является детерминированной и предоставляет меньше возможностей для настройки деструкторов. Такой подход допустим для высокоуровневых языков, таких как C# и Java.Беспроигрышная ситуация это золотая середина между безопасностью и функциональностью. Rust выполняет автоматическое управление памятью без сборщика мусора через владение. Для этого используются простые, но мощные принципы владения:
- Все объекты динамической памяти (в куче) принадлежат только одной переменной-владельцу.
- Когда переменная выходит из области видимости, объект удаляется.
- Когда объект присваивается другой переменной, происходит передача (перемещение) права собственности.
Следующий фрагмент кода иллюстрирует эти принципы:
fn main()
{
let s1 = String::from("hello");
let s2 = s1;
// println!("s1 is: {}, s2 is: {}", s1, s2);
let s3 = s2.clone();
println!("s2 is: {}, s3 is: {}", s2, s3);
print_if_not_empty(s3);
// println!("is s3 still valid? {}",s3);
let s4 = returns_string();
println!("Is s4 valid? {}", s4);
}
fn print_if_not_empty(s : String)
{
if ! s.is_empty()
{
println!("String s: {}", s)
}
}
fn returns_string() -> String
{
String::from("hello world!")
}
Этот код работает правильно, но не будет компилироваться, если убрать символы комментариев со с трок с операторами печати. И вот почему:
- Когда s1 присваивается s2, значение s1 фактически перемещается в s2. Переменная s2 указывает на адрес динамической памяти s1, и s1 больше не является допустимой переменной. Обращение к s1, как в первом аргументе оператора печати, приводит к ошибке компиляции.
- Создание s3 демонстрирует создание полной (глубокой) копии без необходимости передачи владения. Переменная s3 является клоном s2 и располагается в другой области памяти. Право собственности не передаётся, поэтому и s2, и s3 будут действительными. В большинстве языков программирования глубокие копии делаются по умолчанию при присваивании, что может быть затратно для больших объектов. Передача владения по умолчанию вместо глубокой копии не приводит к снижению производительности.
- Аналогично присваиванию, владение передаётся при передаче объекта в функцию. Когда s3 передается в print_if_not_empty(), его значение перемещается в локальную переменную s, когда s выходит из области действия в конце функции, память освобождается. Как и следовало ожидать, попытка печати s3 после этого приводит к ошибке компиляции. Если после вызова функции требуется s3, существуют альтернативы, которые мы сейчас не стали рассматривать.
- Те же правила применяются при возврате значений из функций. При возврате значения передаётся владение из области действия локальной функции. Поэтому память не освобождается в конце функции. В приведённом выше примере последовательность «hello world!» перемещается в s4.
В конце программы будут удалены только s2 и s4, т.к. s1, s3 и s были перемещены. С гарантией только одного владельца, все значения детерминированно очищаются, как только они больше не нужны. Такой подход даёт ещё одно преимущество — выявления ошибок во время компиляции.При первом знакомстве с принципами владения, они показались мне избыточными (мне же, в отличии от автора оригинальной заметки, так не показалось). Если функция вызывает перемещение объектов, то кодировать в Rust становится сложнее. Мне также показалось странным, что для управления памятью не используются указатели. Вместо указателей в Rust используются ссылки, но это тема для отдельной заметки. В остальном становится ясно, что Rust старается быть воплощением безопасности, производительности и выявлять ошибки при каждом удобном случае. А вы что думаете по сказанному выше?Другие заметки цикла:
- С лёгким налётом ржавчины или куда делся NULL
- С лёгким налётом ржавчины или немного о владении
===========
Источник:
habr.com
===========
===========
Автор оригинала: Imaculate
===========Похожие новости:
- [Разработка веб-сайтов, CSS, Программирование, Java] От студента до учителя: как разобраться в веб-разработке, если это не твой профиль
- [Программирование, Конференции] Современный фронтенд без ошибок и костылей. 8 полезных докладов конференции DUMP
- [Разработка веб-сайтов, Программирование, .NET, Тестирование веб-сервисов] Как я решил протестировать нагрузочную способность web сервера
- [Программирование, Проектирование и рефакторинг, Разработка под Android, Kotlin] Пишем под android с Elmslie
- [Программирование, ReactJS, Управление разработкой, TypeScript] Wrike переходит с Dart на новый стек. Какой?
- [Программирование микроконтроллеров, Производство и разработка электроники, Интернет вещей, DIY или Сделай сам] Разработка защищённого WEB интерфейса для микроконтроллеров
- [Python, Программирование] Полиморфизм в Python (перевод)
- [Системное администрирование, PostgreSQL, Администрирование баз данных] Noisia — генератор аварийных и нештатных ситуаций в PostgreSQL
- [Python, Программирование] Превращаем клавиатуру в пианино с помощью python
- [Информационная безопасность, Разработка веб-сайтов, PHP, Программирование] Слабые места PHP: думай как хакер
Теги для поиска: #_programmirovanie (Программирование), #_rust, #_rust, #_vladenie (владение), #_vladenie_pamjatju (владение памятью), #_sborschik_musora (сборщик мусора), #_gc, #_programmirovanie (
Программирование
), #_rust
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:47
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Предыдущая заметка получилась не такая, как я задумывал. Но вызвала небольшую дискуссию. Может быть и в этот раз получится подискутировать. Или получится не так. В любом случае хотелось бы продолжить воровать тексты у богатых и переводить их бедным, т.е. делиться с общественностью пусть даже иногда для кого-то очевидными вещами. Поговорим за динамическую память? Отчасти Rust интересен из-за ряда непопулярных решений в дизайне языка, которые позволяют достичь такого же, а иногда и лучшего результата, чем в других языках. Хорошим примером является управление памятью, а именно управление динамической памятью. Управление памятью может осуществляться двумя способами — явно или автоматически.Явное управление памятью поддерживается в языках системного программирования, таких как C и C++. При явном управлении памятью программист обладает большой свободой. Но с большой властью приходит большая ответственность. При необходимости память может быть динамически выделена в куче и должна быть освобождена, когда в ней больше нет необходимости. При этом важное значение имеет время освобождения памяти — слишком раннее освобождение может привести к использованию недействительных ссылок, позднее освобождение приводит к потере памяти. Программист также должен быть осторожен, чтобы не освободить память более одного раза. Если эти требования не выполняются, могут возникнуть следующие ошибки.1. Утечка памятиУтечка памяти (memory leak) происходит в случае, когда память освобождается слишком поздно (или не освобождается вовсе). Ниже приведён пример, в котором память вообще не освобождается. Оператор new выделяет память в куче и переменная p указывает на адрес массива. Память массива должна быть освобождена, когда массив больше не нужен, но в коде примера этого не происходит. В конечном итоге память, конечно, освобождается, так как ОС очищает ресурсы после завершения программы. Проблема становится более очевидной в случае, если код многократно повторяется в долго работающей программе или на устройстве с ограниченной памятью. #include <iostream>
using namespace std; int main() { cout << "Before allocate" << endl; int * p = new int[10]; // ... other program logic cout << "After allocate" << endl; // p is not deleted } #include <iostream>
using namespace std; char *returns_pointer() { char c = 'a'; return &c; } int main() { char *cp = returns_pointer(); cout << "Result: " << *cp << endl; } #include <iostream>
using namespace std; int main() { int *A = new int[10]; cout << "After delete A: " << A << endl; delete A; int *B = new int[10]; delete A; cout << "After second delete A: " << A << endl; delete B; cout << "After delete B: " << B << endl; }
fn main()
{ let s1 = String::from("hello"); let s2 = s1; // println!("s1 is: {}, s2 is: {}", s1, s2); let s3 = s2.clone(); println!("s2 is: {}, s3 is: {}", s2, s3); print_if_not_empty(s3); // println!("is s3 still valid? {}",s3); let s4 = returns_string(); println!("Is s4 valid? {}", s4); } fn print_if_not_empty(s : String) { if ! s.is_empty() { println!("String s: {}", s) } } fn returns_string() -> String { String::from("hello world!") }
=========== Источник: habr.com =========== =========== Автор оригинала: Imaculate ===========Похожие новости:
Программирование ), #_rust |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:47
Часовой пояс: UTC + 5