Нужно ли чистить память перед exit

Обновлено: 06.07.2024

Завершает вызывающий процесс. exit Функция завершает ее после очистки _exit и _Exit немедленно завершает ее.

не используйте этот метод для завершения работы приложения универсальная платформа Windows (UWP), за исключением сценариев тестирования или отладки. в соответствии с политиками Microsoft Storeне разрешено закрывать приложения магазина программным способом или с помощью пользовательского интерфейса. Дополнительные сведения см. в статье жизненный цикл приложения UWP. Дополнительные сведения о приложениях Windows 10 см. в разделе Практические руководства для приложений Windows 10.

Синтаксис

Параметры

status
Код состояния завершения.

Remarks

Хотя exit _Exit _exit вызовы и не возвращают значение, значение в status становится доступным для среды размещения или ожидающего вызова процесса, если он существует, после завершения процесса. Как правило, вызывающий объект устанавливает status значение 0, чтобы указать на нормальную работу, или на какое-либо другое значение, указывающее на ошибку. status Значение доступно для команды пакетной службы и представляется ERRORLEVEL одной из двух констант: EXIT_SUCCESS , которое представляет значение 0, или EXIT_FAILURE , которое представляет значение 1.

exit Функции, _Exit , _exit , quick_exit , _cexit и _c_exit ведут себя следующим образом.

Функция Описание
exit Выполняет полные процедуры завершения библиотеки C, завершает процесс и предоставляет полученный код состояния среде узла.
_Exit Выполняет минимальные процедуры завершения библиотеки C, завершает процесс и предоставляет полученный код состояния среде узла.
_exit Выполняет минимальные процедуры завершения библиотеки C, завершает процесс и предоставляет полученный код состояния среде узла.
quick_exit Выполняет быстрые процедуры завершения библиотеки C, завершает процесс и предоставляет полученный код состояния среде узла.
_cexit Выполняет полные процедуры завершения библиотеки C и возвращает управление вызывающему объекту. Не завершает процесс.
_c_exit Выполняет минимальные процедуры завершения библиотеки C и возвращает управление вызывающему объекту. Не завершает процесс.

При вызове exit функции, _Exit или _exit , деструкторы для любых временных или автоматических объектов, существующих во время вызова, не вызываются. Автоматический объект — это не статический локальный объект, определенный в функции. Временный объект — это объект, созданный компилятором, например значение, возвращаемое вызовом функции. Чтобы уничтожить автоматический объект перед вызовом exit , _Exit или _exit , явно вызовите деструктор для объекта, как показано ниже:

Не используйте DLL_PROCESS_ATTACH для вызова exit из DllMain . Чтобы выйти DLLMain из функции, вернитесь FALSE из DLL_PROCESS_ATTACH .

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

В предыдущей главе уже обсуждалось, что локальные переменные кладутся на стек и существую до тех пор, пока мы не вышли из функции. С одной стороны, это позволяет автоматически очищать память, с другой стороны, существует необходимость в переменных, время жизни которых мы можем контролировать самостоятельно. Кроме того, нам необходимо динамическое выделение памяти, когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выдеение памяти – достаточно дорогостоящая операция.

Для выделения памяти на куче в си используется функция malloc (memory allocation) из библиотеки stdlib.h

Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.

После того, как мы поработали с памятью, необходимо освободить память функцией free.
Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.

Здесь (int *) – приведение типов. Пишем такой же тип, как и у указателя.
size * sizeof(int) – сколько байт выделить. sizeof(int) – размер одного элемента массива.
После этого работаем с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память.

Теперь представим на рисунке, что у нас происходило. Пусть мы ввели число 5.

Функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может пользоваться и любым другим адресом.
Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У нас будет гарантия, что компьютер не отдаст нашу память кому-то ещё. Когда мы вызываем функцию free, то мы освобождаем память, то есть говорим компьютеру, что эта память может быть использована кем-то другим. Он может использовать нашу память, а может и нет, но теперь у нас уже нет гарантии, что эта память наша. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.

Это очень похоже на съём номера в отеле. Мы получаем дубликат ключа от номера, живём в нём, а потом сдаём комнату обратно. Но дубликат ключа у нас остаётся. Всегда можно зайти в этот номер, но в нём уже кто-то может жить. Так что наша обязанность – удалить дубликат.

Иногда думают, что происходит "создание" или "удаление" памяти. На самом деле происходит только перераспределение ресурсов.

Освобождение памяти с помощью free

Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?

  • 1. Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
  • 2. Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.

Работа с двумерными и многомерными массивами

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

  • 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
  • 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.

Создадим "треугольный" массив и заполним его значениями

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

calloc

Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис

realloc

Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:

Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.

Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.

Ошибки при выделении памяти

1. Бывает ситуация, при которой память не может быть выделена. В этом случае функция malloc (и calloc) возвращает NULL. Поэтому, перед выделением памяти необходимо обнулить указатель, а после выделения проверить, не равен ли он NULL. Так же ведёт себя и realloc. Когда мы используем функцию free проверять на NULL нет необходимости, так как согласно документации free(NULL) не производит никаких действий. Применительно к последнему примеру:

Хотелось бы добавить, что ошибки выделения памяти могут случиться, и просто выходить из приложения и выкидывать ошибку плохо. Решение зависит от ситуации. Например, если не хватает памяти, то можно подождать некоторое время и после этого опять попытаться выделить память, или использовать для временного хранения файл и переместить туда часть объектов. Или выполнить очистку, сократив используемую память и удалив ненужные объекты.

2. Изменение указателя, который хранит адрес выделенной области памяти. Как уже упоминалось выше, в выделенной области хранятся данные об объекте - его размер. При удалении free получает эту информацию. Однако, если мы изменили указатель, то удаление приведёт к ошибке, например

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

3. Использование освобождённой области. Почему это работает в си, описано выше. Эта ошибка выливается в другую – так называемые висячие указатели (dangling pointers или wild pointers). Вы удаляете объект, но при этом забываете изменить значение указателя на NULL. В итоге, он хранит адрес области памяти, которой уже нельзя воспользоваться, при этом проверить, валидная эта область или нет, у нас нет возможности.

Эта программа отработает и выведет мусор, или не мусор, или не выведет. Поведение не определено.

Если же мы напишем

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

4. Освобождение освобождённой памяти. Пример

Здесь дважды вызывается free для переменной a. При этом, переменная a продолжает хранить адрес, который может далее быть передан кому-нибудь для использования. Решение здесь такое же как и раньше - обнулить указатель явно после удаления:

5. Одновременная работа с двумя указателями на одну область памяти. Пусть, например, у нас два указателя p1 и p2. Если под первый указатель была выделена память, то второй указатель может запросто скомпрометировать эту область:

Рассмотрим код ещё раз.

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

А вот здесь происходит непредвиденное. Мы решили выделить под p2 новый участок памяти. realloc гарантирует сохранение контента, но вот сам указатель p1 может перестать быть валидным. Есть разные ситуации. Во-первых, вызов malloc мог выделить много памяти, часть которой не используется. После вызова ничего не поменяется и p1 продолжит оставаться валидным. Если же потребовалось перемещение объекта, то p1 может указывать на невалидный адрес (именно это с большой вероятностью и произойдёт в нашем случае). Тогда p1 выведет мусор (или же произойдёт ошибка, если p1 полезет в недоступную память), в то время как p2 выведет старое содержимое p1. В этом случае поведение не определено.

Два указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.

Различные аргументы realloc и malloc.

При вызове функции malloc, realloc и calloc с нулевым размером поведение не определено. Это значит, что может быть возвращён как NULL, так и реальный адрес. Им можно пользоваться, но к нему нельзя применять операцию разадресации.
Вызов realloc(NULL, size_t) эквиваленте вызову malloc(size_t).
Однако, вызов realloc(NULL, 0) не эквивалентен вызову malloc(0) :) Понимайте это, как хотите.

Примеры

1. Простое скользящее среднее равно среднему арифметическому функции за период n. Пусть у нас имеется ряд измерений значения функции. Часто эти измерения из-за погрешности "плавают" или на них присутствуют высокочастотные колебания. Мы хотим сгладить ряд, для того, чтобы избавиться от этих помех, или для того, чтобы выявить общий тренд. Самый простой способ: взять n элементов ряда и получить их среднее арифметическое. n в данном случае - это период простого скользящего среднего. Так как мы берём n элементов для нахождения среднего, то в результирующем массиве будет на n чисел меньше.

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

2. Сортировка двумерного массива. Самый простой способ сортировки - перевести двумерный массив MxN в одномерный размером M*N, после чего отсортировать одномерный массив, а затем заполнить двумерный массив отсортированными данными. Чтобы не тратить место под новый массив, мы поступим по-другому: если проходить по всем элементам массива k от 0 до M*N, то индексы текущего элемента можно найти следующим образом:
j = k / N;
i = k - j*M;
Заполним массив случайными числами и отсортируем

3. Бином Ньютона. Создадим треугольную матрицу и заполним биномиальными коэффициентами

Если Вы желаете изучать этот материал с преподавателем, советую обратиться к репетитору по информатике

email

Всё ещё не понятно? – пиши вопросы на ящик

Что делать, чтобы предотвратить утечку памяти? Спасибо!

На этот вопрос было бы гораздо проще ответить, если бы вы предоставили минимальный код, который, по вашему мнению, содержит утечку памяти. Кажется, вы не понимаете терминологию. Утечка памяти заключается в накоплении все большего количества выделенной памяти (которая больше никогда не будет использоваться) во время работы процесса. Это бессмысленно в контексте прекращения процесса. Однако при обычном завершении вы можете получить некоторое представление о том, произошла утечка памяти или нет, а именно, проверяя, есть ли еще выделенные блоки. Некоторые инструменты делают это. Это совершенно бессмысленно для вызова exit (или abort ), когда деструкторы локальных объектов не вызываются. Я подозреваю, что вы основываете свое мнение на такой бессмысленной проверке. @Alf: Это совсем не "бессмысленно". Наличие памяти, которая еще используется при завершении программы, означает, что в вашем коде есть утечка. Это то, к чему относится, например, valgrind . И, да, exit / abort завершают работу программы, не вызывая деструкторы для объектов автоматического хранения .

4 ответа

exit не вызывает деструкторы любых объектов на основе стека, поэтому, если эти объекты выделяют какую-либо память внутри, тогда да, эта память будет протекать.

На практике это, вероятно, не имеет значения, так как любая вероятная операционная система все равно восстановит память. Но если деструкторы должны были сделать что-нибудь еще, у вас возникнет проблема.

По этой причине выход

не очень хорошо сочетается с С++. Вам лучше просто позволить вашей программе вернуться из main, чтобы выйти, или если вам нужно выйти из внутренней функции, выбрасывая исключение, что приведет к разворачиванию стека вызовов и, следовательно, деструкторов, которые будут вызваны.

«что приведет к разматыванию стека вызовов» - при условии, что исключение перехвачено. Если это не так, то это определяется реализацией независимо от того, будет ли стек размотан или нет. Итак, что-то вроде int main() < try < /* program goes here */ >catch (const ExitException &e) < return e.value; >catch (. ) < return 1; >return 0; > делает свое дело, где ExitException - это класс исключений, который я изобрел для удержания кода выхода. Очевидно, что регистрация также полезна, и в режиме отладки может быть лучше не перехватывать исключения, если отладчик делает что-нибудь полезное с неперехваченными исключениями. О да, я предполагал, что исключение будет поймано. На практике реализации, которые я использовал, разворачивают стек даже для неперехваченного исключения, но вы не должны полагаться на неопределенное поведение, если можете его избежать. @JohnB: это не неопределенное поведение. Это определяется реализацией. Я видел немедленное прекращение при выдаче неисследованного исключения много раз. Понятно - я думаю, что все еще справедливо будет сказать, что если вы хотите правильно очистить выход, то вызов exit (), вероятно, не должен быть вашим первым выбором

При использовании функции exit ваша программа будет завершена, и вся выделенная им память будет выпущена. Не будет утечки памяти.

EDIT: Из ваших комментариев я могу понять, что вас беспокоит, что ваши объекты не уничтожаются до завершения (т.е. Их деструктор не вызывается). Однако это не является утечкой памяти, так как память освобождается процессом и становится доступной системе. Если вы рассчитываете на деструкторы объектов для выполнения операций, важных для вашего рабочего процесса, я предлагаю вернуть код ошибки вместо использования exit и распространить этот код ошибки до main().

В соответствии со стандартом вызов exit() во время уничтожения объекта со статической продолжительностью хранения приводит к поведению undefined. Вы это делаете?


Сегодня я хочу немного приоткрыть свет над тем, как бороться с утечкой памяти в Си или С++.

На Хабре уже существует две статьи, а именно: Боремся с утечками памяти (C++ CRT) и Утечки памяти в С++: Visual Leak Detector. Однако я считаю, что они недостаточно раскрыты, или данные способы могут не дать нужного вам результата, поэтому я хотел бы по возможности разобрать всем доступные способы, дабы облегчить вам жизнь.

Windows — разработка
Начнем с Windows, а именно разработка под Visual Studio, так как большинство начинающих программистов пишут именно под этой IDE.


Для понимания, что происходит, прикладываю реальный пример:

А также есть Student.h и Student.c в котором объявлены структуры и функции.

Есть задача: продемонстрировать отсутствие утечек памяти. Первое, что приходит в голову — это CRT. Тут все достаточно просто.

А перед return 0 нужно прописать это: _CrtDumpMemoryLeaks(); .

В итоге, в режиме Debug, студия будет выводить это:

Супер! Теперь вы знаете, что у вас утечка памяти. Теперь нужно устранить это, поэтому необходимо просто узнать, где мы забываем очистить память. И вот тут возникает проблема: а где, собственно, выделялась эта память?

После того, как я повторил все шаги, я выяснил, что память теряется где-то здесь:

Но как так — то? Я же все освобождаю? Или нет?

И тут мне сильно не хватало Valgrind, с его трассировкой вызовов.

В итоге, после 15 минут прогугливания, я нашел аналог Valgrind — Visual Leak Detector. Это сторонняя библиотека, обертка над CRT, которая обещала показывать трассировку! Это то, что мне необходимо.

Чтобы её установить, необходимо перейти в репозиторий и в assets найти vld-2.5.1-setup.exe

Правда, последнее обновление было со времен Visual Studio 2015, но оно работает и с Visual Studio 2019. Установка стандартная, просто следуйте инструкциям.

Преимущество этой утилиты заключается в том, что можно не запускать в режиме debug (F5), ибо все выводится в консоль. В самом начале будет выводиться это:

И вот, что будет выдавать при утечке памяти:

Вот, я вижу трассировку! Так, а где строки кода? А где названия функций?


Ладно, обещание сдержали, однако это не тот результат, который я хотел.

Остается один вариант, который я нашел в гугле: моментальный снимок памяти. Он делается просто: в режиме debug, когда доходите до return 0, необходимо в средстве диагностики перейти во вкладку "Использование памяти" и нажать на "Сделать снимок". Возможно, у вас будет отключена эта функция, как на первом скриншоте. Тогда необходимо включить, и перезапустить дебаг.



После того, как вы сделали снимок, у вас появится под кучей размер. Я думаю, это сколько всего было выделено памяти в ходе работы программы. Нажимаем на этот размер. У нас появится окошко, в котором будут содержаться объекты, которые хранятся в этой куче. Чтобы посмотреть подробную информацию, необходимо выбрать объект и нажать на кнопку "Экземпляры представления объекта Foo".



Да! Это победа! Полная трассировка с местоположением вызовов! Это то, что было необходимо изначально.


Linux — разработка
Теперь, посмотрим, что творится в Linux.

В Linux существует утилита valgrind. Чтобы установить valgrind, необходимо в консоли прописать sudo apt install valgrind (Для Debian-семейства).

Я написал небольшую программу, которая заполняет динамический массив, но при этом, не очищается память:

Скомпилировав программу с помощью CLang, мы получаем .out файл, который мы подкидываем valgrind'у.

С помощью команды valgrind ./a.out . Как работает valgrind, думаю, есть смысл описать в отдельной статье, а сейчас, как выполнится программа, valgrind выведет это:

Таким образом, valgrind пока показывает, сколько памяти было потеряно. Чтобы увидеть, где была выделена память, необходимо прописать --leak-check=full , и тогда, valgrind, помимо выше описанного, выведет это:

Конечно, тут не указана строка, однако уже указана функция, что не может не радовать.

Есть альтернативы valgrind’у, такие как strace или Dr.Memory, но я ими не пользовался, да и они применяется в основном там, где valgrind бессилен.

Выводы

Я рад, что мне довелось столкнуться с проблемой поиска утечки памяти в Visual Studio, так как я узнал много новых инструментов, когда и как ими пользоваться и начал разбирать, как работают эти инструменты.

Как очистить память на Андроиде

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

Посмотреть статистику загруженности внутренней памяти и SD-карты можно в разделе «Память» («Хранилище», «Накопители») настроек устройства, либо открыв приложение «Проводник», после чего уже переходить к очистке и оптимизации.

Очистка кэша

Большинство установленных на смартфоне или планшете приложений имеют собственную папку, где хранятся временные файлы (кэш), необходимые, как правило, для ускорения загрузки и экономии интернет-трафика. Ничего особо важного, типа данных для авторизации, в кэше обычно не хранится, поэтому можно смело его очищать. Для этого нужно открыть «Настройки» — «Память» — «Данные кэша» и в открывшемся окне нажать «Да». Очистка кэша может занять несколько минут.


Кэш некоторых приложений нельзя очистить таким образом, придется сделать это в персональном режиме. В браузере Google Chrome следует открыть «Настройки» — «Дополнительно» — «Личные данные» — «Очистить историю». Поставить здесь галочку возле «Изображения и другие файлы, сохраненные в кэше», выбрать период, например, «Все время» и нажать «Удалить данные».


Из приложений одним из самых прожорливых до постоянной памяти является Telegram. Если он установлен и имеется много подписок, стоит озаботиться оптимизаций его настроек. Открыть «Настройки» — «Данные и память» — «Использование памяти».


Установить «Хранить медиа» 3 дня. Затем произвести очистку кэша, нажав «Очистить кэш Telegram». В открывшемся окне можно выбрать, что конкретно нужно удалить, таким образом, чтобы не получилось конфликта с экономией трафика.


Перенос приложений на SD-карту

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

Если в открывшемся окне будет присутствовать кнопка «Перенести на: MicroSD» или аналогичная, значит перенос доступен и можно его осуществить нажав сюда. После переноса здесь появится кнопка «Перенести на: Память устройства», с помощью которой можно будет перенести приложение обратно на ПЗУ девайса.


В Android 5 и последних версиях операционной системы данная функция недоступна, однако здесь и в других версиях Android можно воспользоваться опцией выбора SD-карты в качестве основной (внутренней) памяти, после чего новые приложения и их обновления будут полностью или частично устанавливаться туда. Там же будут кэшироваться данные. При этом карта памяти отформатируется, соответственно, ее содержимое будет удалено. Для использования на другом устройстве ее снова придется отформатировать. Карту памяти для этого лучше использовать быструю, UHS-I или выше.

Для смены основной памяти открываем «Настройки» — «Хранилище» («Память», «Носители») — «SD-карта» — «Меню» — «Настройки хранилища».


Далее «Внутренняя память» — «SD-карта: форматировать».


Для того, чтобы перенести данные на отформатированную карту памяти, нажимаем на «SD-карта», далее «Меню» — «Перенести данные» — «Перенести контент».


Для превращения установленной в качестве внутренней памяти SD-карты в прежней формат, с возможностью использования на другом устройстве, вновь заходим в настройки «Хранилища», жмем «SD-карта» — «Меню» — «Портативный носитель» — «Форматировать». Соответственно, все ее содержимое будет ликвидировано.


Перенос файлов в облачное хранилище


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

Очистка с помощью сторонних приложений

Очистку памяти можно осуществить также с помощью одного из специальных приложений — например, CCleaner, KeepClean, AVG Cleaner. Перечисленные утилиты бесплатны в базовой версии и достаточно просты в использовании.

В программе CCleaner нужно открыть «Быструю очистку». После анализа хранилища отобразится все, что в принципе можно удалить для освобождения свободного места в ПЗУ. В разделе «Удаление безопасно» представлен в основном кэш приложений, который можно смело зачищать, в разделе «На ваше усмотрение» — различные файлы и данные. Выбираем подлежащее удалению и жмем «Завершить очистку». В платной Pro-версии CCleaner доступна автоматическая очистка по расписанию.

Читайте также: