[БЕЗ_ЗВУКА] Мы с вами говорили, что указатели похожи на итератор. И давайте рассмотрим пример. У нас есть вектор целых чисел v, в котором есть числа 1, 2, 3, 4, 5. И в отдельной области видимости у нас объявлен итератор iter, в который записан результат поиска пятёрки в нашем векторе v. Естественно, этот итератор будет валидным, он будет указывать внутрь вектора, потому что пятёрка в нашем векторе есть. И давайте зададим вопрос — что станет с пятёркой в нашем векторе v после того, как итератор iter выйдет из области видимости? Вы уже работали с итераторами, и я уверен, что сразу же ответили на этот вопрос, что ничего не произойдет с пятёркой, потому что итератор просто указывает позицию в контейнере и никак не владеет, не управляет значением, на которое он указывает. Поэтому сам итератор разрушился, он вышел из области видимости, но само значение в векторе никуда не делось, оно там продолжает храниться, и мы можем им пользоваться. Теперь давайте рассмотрим другой пример. Мы в отдельной области видимости выделяем в куче память для хранения целого числа и инициализируем ее пятеркой. И указатель на эту память мы записываем в переменную pFive. И давайте зададим аналогичный вопрос — а что произойдёт с пятёркой в куче, с этой пятёркой, после того как указатель pFive выйдет из области видимости? И ответ на этот вопрос такой же, как и на предыдущий, — ничего, пятёрка останется в куче, память под неё останется выделенной, и произойдёт утечка памяти. Что это значит? Это значит, что эта память выделенная на куче, она будет числиться за нашим процессом до того, как этот процесс завершится, и у нас не будет никакой возможности к этой памяти обратиться и как-то ее освободить, потому что мы потеряли адрес этой памяти, указатель на неё, он только что вышел из области видимости, и всё, мы больше не можем обратиться к этому адресу, поэтому мы больше не можем обратиться к этой памяти. На первый взгляд такая проблема, как утечка памяти может показаться не такой уж и большой. Подумаешь, осталось висеть в памяти одно значение типа int. Сейчас у компьютеров много оперативной памяти, одно значение int не жалко. Процесс закончит свою работу, всё равно вся память ему выделенная освободится, всё будет нормально. Но на самом деле всё не так просто. Давайте мы рассмотрим пример. Он, правда, немножко искусственный, но суть отражает. Давайте мы напишем программу, которая будет считывать со входа число n и будет считать сумму n случайных 64-битных чисел. Кстати, мы раньше на наших курсах не рассматривали, как с помощью стандартной библиотеки генерировать случайные числа, сейчас поверхностно познакомимся заодно. Я подключаю заголовочный файл random, объявляю переменную типа int, считываю ее с экрана, дальше я объявляю переменную магического типа mt19937_64, это генератор случайных чисел, объявляю переменную 64-битного типа, в которую мы будем складывать сумму, а дальше запускаю цикл от 0 до m и делаю вот что: объявляю переменную типа x, присваиваю ей случайное значение, увеличиваю сумму на x и в конце работы программы вывожу. Вот такая программа. Давайте мы ее скомпилируем, запустим, введем, например, 10, и у нас вывелось какое-то большое число. Программа наша работает, считывает число, считает сумму n случайных чисел, и пока с этой программой всё нормально, в ней нет никаких утечек, всё не страшно. Давайте мы в этой программе сделаем некоторые изменения. Давайте мы переменную x, сейчас переменная x у нас создаётся на стеке. Давайте мы это поменяем, мы будем выделять в куче память для хранения очередного случайного числа. И будем туда его сохранять и прибавлять к нашей сумме. Давайте запустим нашу программу, опять укажем 10, опять вывелось какое-то число. И смотрите, мы память выделяем, вот указатель на нее x, мы туда что-то записали, воспользовались этой переменной, и потом переменная x вышла из области видимости, то есть как раз произошло то, о чём я говорил, произошла утечка. При этом наша программа отработала, всё нормально, никаких проблем не возникло, она там спокойно завершилась, посчитала какую-то сумму. И действительно, всё вроде ничего. Теперь давайте мы с вами представим веб-сервер, который должен обрабатывать входящие запросы 24 на 7. Если в нём будет утекать память, то в какой-то момент он просто перестанет быть в состоянии отвечать на ваши запросы. И давайте мы эту ситуацию попробуем немножечко смоделировать с помощью нашего примера. У меня на экране появился мониторинг занятой памяти на моём компьютере. И давайте мы возьмём нашу программу, запустим её, и в качестве n — количества чисел, которые мы хотим просуммировать, введем большое число, например, 750 миллионов. И мы запустим нашу программу, она начала работать. И пока она работает, мы начнём смотреть на график занятой памяти в системе. Смотрите, что происходит, занятой памяти становится всё больше, а свободной всё меньше, и по мере наша программа продолжает работать, здесь красный квадратик говорит о том, что она работает, мы видим, что занятой памяти становится всё больше, и мы потихонечку приближаемся к верхней границе, у нас свободная память потихоньку заканчивается. Представьте, что мы сейчас не сумму считаем, а работает наш веб-сервер, и на этом компьютере свободная память потихоньку заканчивается. Программа отработала, вы видите, что свободной памяти стало снова достаточно. Я специально подбирал число 750 миллионов, просто, если указать больше, то память совсем кончится, и мой ноутбук станет неотзывчивым, я просто не смогу продолжить записывать это видео. Мы увидели, что действительно проблема утечки серьезная, мы можем целых 16 гигабайт оперативной памяти достаточно быстро потребить даже на такую просто операцию, как суммирование n чисел. Хорошо, надеюсь, эта демонстрация убедила вас в том, что проблема утечки — это проблема серьёзная. Как же с ней бороться? Бороться с ней не так-то трудно. Смотрите, у нас есть оператор new, который выделяет память, значит, должен быть какой-то оператор, который делает симметричное действие — память освобождает. И этот оператор действительно есть, это оператор delete. Он принимает указатель, x у нас — это указатель, и он освобождает память, выделенную ранее с помощью оператора new. Мы его с вами применили внутри цикла, то есть мы внутри цикла выделяем память, как-то с ней работаем, а потом освобождаем. Давайте скомпилируем нашу программу, она у нас успешно скомпилировалась, а дальше сделаем вот что. Снова откроем мониторинг памяти, снова запустим нашу программу и снова укажем ей 750 миллионов и нажмем enter. Наша программа работает, она генерирует числа, складывает их, но, видите, количество выделенной памяти не растет. В первой демонстрации, как только я запустил программу, здесь сразу график пошёл вверх. Но сейчас наша программа считает сумму, но количество памяти, выделенной в системе, не изменяется, потому что мы выделили 64 бита, освободили 64 бита, выделили — освободили. Таким образом, вы видите, что оператор delete действительно освобождает выделенную память и тем самым позволяет защититься от утечек. давайте остановим нашу программу и подведём итоги. В этом видео мы с вами узнали, что оператор delete позволяет освободить память, выделенную через оператор new. При этом делать это нужно обязательно, потому что автоматически память не освобождается. Если не освобождать память, выделенную с помощью оператора new, то происходит утечка памяти, которая особенно опасна для процессов, которые должны работать бесперебойно.