Что такое утечка памяти c

Обновлено: 21.11.2024

В загруженный архив включен тестовый скрипт:

Отправка

  • заполненный рабочий лист.txt
  • исправлена ​​ошибка memleak.c
  • исправлено нарушение памяти.c

Чтобы отправить из командной строки, запустите:

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

Утечки памяти

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

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

В malloc() выделяется новая память, но она никогда не назначается указателю. Нет способа отслеживать память и освобождать ее; таким образом, у нас есть утечка памяти. Эта программа еще более ужасна тем, что она вечно зацикливается с утечкой памяти. Если запустить, он в конечном итоге замедлит работу вашего компьютера. НЕ ЗАПУСКАЙТЕ ЭТУ ПРОГРАММУ БЕЗ ТЩАТЕЛЬНОГО НАБЛЮДЕНИЯ. Если вы решите запустить его, быстро уничтожьте его.

Обычно утечки памяти менее опасны. Давайте рассмотрим более распространенный пример.

Это простая программа, использующая целочисленный указатель a в двух выделениях. Во-первых, он выделяет одно целое число и присваивает выделенной памяти значение 10. Затем он использует a для ссылки на массив целых чисел длины 3. Он выводит значения для обоих случаев. Вот некоторый вывод программы и компиляция. (-g предназначен для компиляции с отладочной информацией, которая станет важной позже.)

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

При втором выделении и назначении a предыдущее выделение не освобождается d. Присвоение второго выделения из calloc() перезапишет предыдущее значение указателя, которое использовалось для ссылки на начальное выделение, то есть с помощью malloc() . В результате предыдущее значение указателя и память, на которую он ссылался, теряются и не могут быть освобождены; классическая утечка памяти.

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

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

Проверьте раздел LEAK SUMMARY, и вы обнаружите, что 16 байтов были «определенно» потеряны. Давайте повторно запустим valgrind с параметром --leak-check, установленным на «полный», чтобы увидеть больше деталей, что дополнительно распечатает СУММУ КУЧИ

В нем перечислены два распределения. Первый вызов malloc выделил 4 байта, размер целого числа. Второе выделение, выделенное 3 целым числам или 12 байтам, с помощью calloc. С помощью этой информации программист может отследить утечку памяти и исправить ее, что вы и сделаете для этой задачи.

Задание 1

Скомпилируйте и запустите memleak.c. Проверьте вывод и попытайтесь понять программу.

Ответьте на следующие вопросы в worksheet.txt :

Запустите valgrind в программе memleak. Сколько байтов определенно потеряно?

В каких строках valgrind указывает на утечку памяти?

Опишите причины каждой утечки памяти.

Попробуйте устранить утечки памяти и проверьте исправление с помощью valgrind. Опишите, как вы устраняли утечки памяти.

Вы отправляете исправленную программу memleak.c вместе с заявкой, и мы проверим, устранили ли вы утечку памяти.

Нарушения памяти

Утечки памяти — это не единственный тип ошибок памяти, которые может обнаруживать valgrind, он также может обнаруживать нарушения памяти. Нарушение памяти — это когда вы обращаетесь к памяти, которой не должны, или обращаетесь к памяти до ее инициализации.

Давайте рассмотрим очень простой пример печати неинициализированного значения.

Проблема с этой программой очевидна; мы выводим значение a без предварительного присвоения ему значения.Эта ошибка может быть обнаружена компилятором с опцией -Wall:

Но другие нарушения памяти сложнее распознать, особенно связанные с массивами. Давайте посмотрим на программу ниже. Вы должны быть в состоянии обнаружить ошибку.

Однако, если бы мы скомпилировали и просто запустили эту программу, вы могли бы и не заметить, что что-то не так:

Об ошибках не сообщается, и печатаются числа до 10, но мы знаем, что на самом деле записываем за пределы в наш массив, и мы не должны этого делать! Valgrind, к счастью, умеет обнаруживать такие ошибки:

Если вы заметили в выходных данных выполнения, есть «Недопустимая запись размера 4», происходящая, когда массив [10] назначается в первом цикле, а затем «Недопустимое чтение размера 4», когда он печатается в следующем цикле. петля. Это довольно простой пример, но недопустимые операции чтения и записи и другие виды нарушений памяти могут вызвать всевозможные проблемы в вашей программе, и их следует исследовать и по возможности устранять.

Задание 2

Скомпилируйте и запустите программу memviolation.c. Выполните следующие задания и ответьте на вопросы в своем рабочем листе.

Опишите вывод и выполнение программы. Это кажется последовательным?

Запустите программу под valgrind. Определите строку кода, вызывающую нарушение памяти, и ее ввод.

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

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

  • Какие операции с указателями вызывают повреждение памяти?
  • Контрольные точки, которые необходимо учитывать при работе с динамическим выделением памяти
  • Сценарии, приводящие к утечке памяти

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

Что может пойти не так?

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

Неинициализированная память

В этом примере p было выделено 10 байт. 10 байтов могут содержать мусорные данные, как показано на рис. 1.

Рисунок 1. Мусорные данные

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

Рекомендуется всегда использовать memset вместе с malloc или всегда использовать calloc .

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

Перезапись памяти

Поскольку для p было выделено 10 байтов, если какой-либо фрагмент кода попытается записать в p значение, равное 11 байтам, то эта операция молча, не сообщая вам, съест один байт из какого-то другого места. Предположим, что указатель q представляет эту память.

Рисунок 2. Исходное содержимое q
Рисунок 3. Перезаписанное содержимое q

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

В этом примере операция memcpy пытается записать 11 байтов в p , тогда как выделено только 10 байтов.

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

Память перечитана

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

В этом примере операция memcpy пытается прочитать 20 байтов из ptr , но ей было выделено только 10 байтов. Это также приведет к нежелательному результату.

Утечка памяти

Утечки памяти могут сильно раздражать. В следующем списке описаны некоторые сценарии, приводящие к утечке памяти.

    Переназначение Я буду использовать пример, чтобы объяснить переназначение.

Рисунок 4. Расположение памяти

memoryArea и newArea были выделены по 10 байтов каждая, и их соответствующее содержимое показано на рис. 4.Если кто-то выполнит оператор, показанный ниже (переназначение указателя)…

тогда на более поздних этапах разработки этого модуля у вас наверняка возникнут трудности. В операторе кода выше разработчик присвоил указатель memoryArea указателю newArea. В результате ячейка памяти, на которую ранее указывал memoryArea, становится потерянной, как показано на рис. 5 ниже. Его нельзя освободить, так как нет ссылки на это место. Это приведет к утечке памяти размером 10 байт.

Рис. 5. Утечка памяти
Рис. 6. Динамически выделяемая память

Если memoryArea освобождается вызовом free, то в результате указатель newArea также станет недействительным. Ячейка памяти, на которую указывал newArea, не может быть освобождена, так как не осталось указателя, указывающего на эту ячейку. Другими словами, ячейка памяти, на которую указывает newArea, становится потерянной и приводит к утечке памяти. Всякий раз, освобождая структурированный элемент, который, в свою очередь, содержит указатель на динамически выделенную область памяти, сначала перейдите к дочерней области памяти ( newArea в примере) и начните освобождение оттуда, возвращаясь к родительскому узлу. Правильная реализация здесь будет такой:

Верните то, что приобрели

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

Всегда следите за всем выделением памяти и освобождайте ее при необходимости. На самом деле, можно разработать механизм для отслеживания этих распределений, например, хранить счетчик в самом узле списка ссылок (но вам также придется учитывать дополнительные накладные расходы этого механизма!).

Доступ к нулевому указателю

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

Обзор

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

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

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

  • Один раздел, в котором хранятся инструкции, которые необходимо выполнить, называется сегментом кода или сегментом текста.
  • Другой сегмент/раздел используется для хранения глобальных переменных, переменных, которые не объявлены внутри функции и имеют время жизни всего приложения.
  • Другой сегмент памяти используется для вызовов функций и хранения всех локальных переменных, этот раздел называется STACK.
    Размер этих трех сегментов, то есть сегмента размера, глобального сегмента и стека, фиксирован и определяется во время компиляции программы, то есть во время компиляции.

И четвертый раздел, который называется HEAP или Dynamic Section, не имеет фиксированного размера. Heap может увеличиваться в соответствии с нашими потребностями. Как мы знаем, мы получаем память для кучи, вызывая функцию malloc в C и когда мы закончили с использованием памяти в куче, мы вызываем функцию free, чтобы освободить или освободить эту конкретную память. В C++, кроме malloc и free, мы также можем использовать оператор new, чтобы получить часть памяти, и оператор удаления, чтобы освободить удалите эту память.

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

Утечки памяти в C происходят по трем основным причинам:

  • мы не освобождаем память, которая больше не нужна
  • мы пытаемся освободить память, но у нас нет ссылки на нее (висячий указатель)
  • мы пытаемся освободить память, используя неправильную функцию

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

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

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

Пример висячего указателя:

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

  • Динамическое выделение памяти malloc без ее освобождения.
  • Динамическое выделение памяти malloc и ее освобождение с помощью удаления.
  • Динамическое выделение памяти с помощью new и отсутствие освобождения с помощью удаления.
  • Динамическое выделение памяти с помощью new[ ] и освобождение с помощью удаления.
  • Динамическое выделение новой памяти и свободное ее освобождение.

Давайте кратко их обсудим-

Динамическое выделение памяти malloc, а не ее освобождение.

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

Динамическое выделение памяти malloc и ее освобождение с помощью удаления

Как мы все знаем, malloc используется для выделения динамической памяти в C, и нам нужно освободить эту память, чтобы избежать утечки памяти, но иногда вместо использования free()
мы используем ключевое слово delete, чтобы освободить эту память, которая используется в C++ для освобождения динамической памяти.

Вот пример:

Динамическое выделение памяти с помощью new и без освобождения с помощью удаления

Динамическое выделение памяти в C++ выполняется с помощью new

Чтобы освободить динамическую память в C++, нам нужно использовать команду удаления, но в большинстве случаев мы забываем освободить память, что вызывает утечку памяти.

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

Динамическое выделение памяти с помощью new[ ] и освобождение с помощью удаления

Динамически выделяет память для 10 целых чисел непрерывного типа int и возвращает указатель на первый элемент последовательности, который назначен p(указатель). p[0] относится к первому элементу, p[1] относится ко второму элементу и так далее.

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

Это освободит весь массив и не будет утечек памяти.

Динамическое выделение новой памяти и ее свободное освобождение.

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

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

  • Операция free() используется в C для освобождения динамической памяти.
  • оператор удаления используется в C++ для освобождения динамической памяти.

Итак, подведем итоги. Утечка памяти — это неправильное использование динамической памяти или кучи памяти, которое приводит к увеличению потребления памяти нашей программой с течением времени.

Время чтения: 3 минуты

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

Что такое утечка памяти? Примеры C++ и C

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

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

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

Обнаружение и визуализация ошибок времени выполнения и памяти с помощью Parasoft Insure++

Один из лучших примеров такого поведения можно увидеть, запустив программу «Hello world», показанную ниже.

Если мы выполним эту программу со следующими аргументами:

Если мы проверим состояние программы в строке 25 непосредственно перед выполнением вызова malloc во второй раз, мы увидим:

  • Переменная string_so_far указывает на строку «hello», которая была присвоена ей в результате предыдущей итерации цикла.
  • Переменная строка указывает на расширенную строку "hello this", которая была назначена на этой итерации цикла.

Эти назначения схематически показаны ниже; обе переменные указывают на блоки динамически выделяемой памяти.

Следующее утверждение:

создаст обе переменные, указывающие на более длинный блок памяти, как показано ниже:

Однако когда это происходит, не остается указателя, указывающего на более короткий блок. Даже если бы вы захотели, память, на которую ранее указывала string_so_far, не может быть восстановлена; теперь он постоянно выделен. Это известно как «утечка памяти». C++ и C часто сталкиваются с этими общими проблемами, поэтому важно выявлять их как можно раньше.

Как найти утечку памяти в C++ и C?

Хотя кнопки «обнаружить утечку памяти» нет, в C++ и C есть средства обнаружения во время выполнения, которые могут помочь. Этот тип ошибки можно диагностировать с помощью средств обнаружения ошибок памяти, таких как Parasoft Insure++. Это показано ниже:

Этот пример называется LEAK_ASSIGN, поскольку он вызывается при переназначении указателя. (PS Другие отладчики памяти часто не делают различия между выдающейся памятью и фактической утечкой памяти, но Insure++ делает это.) В этом случае выдающаяся память — это не потрясающая память, это память, которую вы не освободили, в отличие от фактическая утечка памяти, которую вы не можете освободить.

Типы утечек памяти

Parasoft Insure++ также может автоматически обнаруживать несколько других типов утечек:

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

Если вам нужен инструмент C++ для проверки утечки памяти, вы можете получить бесплатную пробную версию Parasoft Insure++.

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