Ошибки при работе с динамической памятью

Обновлено: 07.07.2024

Здравствуйте, дана такая задача:
Даны матрицы Q (12x9) и С (7x8). Вставить после столбцов с максимальными элементами столбцы из нулей, сдвинув последующие. Количество столбцов в матрицах после вставки должно увеличиться.

Написал вот такой код:

Код условия задачи выполняет, после каждого столбца с макс элементом появляется столбец из 0. Но проблема заключается в другом. При запуске программы случается 1 из вариантов:
1) Программа полностью правильно выполняется и выводит верные значения.

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

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

Думаю что проблема кроется в выделении памяти через realloc. Поэтому хочу узнать, правильно ли я выделил память для дополнительных столбцов, и правильно ли я ее в итоге освободил. Если нет, то как коректно это сделать? И еще хочу спросить, могу ли я использовать **a после очищения памяти для заполнения второго массива, или лучше просто создать и использовать **b? С динамической памятью работаю первый раз.

__________________
Помощь в написании контрольных, курсовых и дипломных работ здесь

Работа с динамической памятью
Нужно наименьший элемент каждой строки, начиная со второй заменить наибольшим элементом.


Работа с динамической памятью
Создаю указатели char *s,*p; s = (char *)malloc(sizeof(char)); потом p = (char *)realloc(s.

Что с динамической памятью?
Прерывается работа программы, еще не научилась нормально выделять ДП. Есть структура и для ее.

тут должен быть malloc, забыл вернуть после эксперимента с изменением

Так а что это такое. Какой смысл вызывать realloc , если вы все равно игнорируете его результат?

И даже если бы этот вызов realloc был сделан формально правильно, почему у вас в комментарии написано "выделение доп места для столбцов"? Матрица у вас хранится построчно. Ваше a - это указатели на строки матрицы. Зачем вы перевыделяете память указателей на строки, если вам нужно добавить столбцы.

Также, чего вы ожидаете от вызова out_mas (a, K, L+maxSTLB) ? В добавленной через realloc памяти все равно содержится мусор. Что по вашему сделает out_mas , когда наткнется на этот мусор?

Так а что это такое. Какой смысл вызывать realloc, если вы все равно игнорируете его результат?

И даже если бы этот вызов realloc был сделан формально правильно, почему у вас в комментарии написано "выделение доп места для столбцов"? Матрица у вас хранится построчно. Ваше a - это указатели на строки матрицы. Зачем вы перевыделяете память указателей на строки, если вам нужно добавить столбцы.

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

Также, чего вы ожидаете от вызова out_mas (a, K, L+maxSTLB)? В добавленной через realloc памяти все равно содержится мусор. Что по вашему сделает out_mas, когда наткнется на этот мусор? Я знаю что там содержится мусор, в комментарии написал что он нужен для проверки выделения памяти под столбцы. То есть я смотрел появилось ли n-е количество столбцов для дальнейшей перестановки.(мусор все равно уходил за пределы выводимой матрицы). Также я экспериментировал с количеством выделяемой памяти через realloc и проверял что именно выводиться. Попробовал еще несколько раз переделать выделение памяти через realloc, ничего не получилось. В итоге решил поступить проще и создать 2ю матрицу, выделив ей память через malloc. Создал еще 1 функцию для копирования элементов в новую матрицу, и для упрощение проверок сделал заполнение матрицы псевдослучайными числами. Если кому-то нужно, то вот полная рабочая программа:
Также буду благодарен если кто-нибудь объяснит как правильно выделить память через realloc в первом случае.

Решение


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

Утечки при работе с динамической памятью
Здравствуйте, отправляю задачу на сервер, пишут:"Утечки памяти". Для входных данных: 3 Вот код: .


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


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

В чем преимущества операций new и delete по сравнению с функциями для работы с динамической памятью
Здравствуйте, совсем не знаю язык Си, подскажите отличая между С++. В чем преимущества операций.


Проблемы во время работы с памятью
Здравствуйте! У меня возникла проблема при нахождении определителя матрицы путём разложения матрицы.

Цель лекции : изучить алгоритмы и приемы чтения-записи, перестановок, поиска и сортировок в динамических массивах , научиться решать задачи с использованием алгоритмов чтения-записи, перестановок, поиска и сортировок в динамических массивах на языке C++.

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

Преимущества:

  • разумное использование динамических структур данных приводит к сокращению объёма памяти, необходимого для работы программы;
  • динамические данные не требуют объявлений их как данных фиксированного размера;
  • ряд алгоритмов более эффективен при реализации их с использованием динамических структур. Например, вставка элемента в статический массив на определенное место требует перемещения части элементов массива. При вставке в середину динамического списка достаточно несколько операторов присваивания .
  • алгоритмы для динамических структур обычно более сложны, трудны для отладки по сравнению с аналогичными для статических данных;
  • использование динамических структур требует затрат на память для ссылок. В некоторых задачах объём памяти, отводимой для ссылок, превосходит объём памяти, выделяемой непосредственно для данных;
  • существуют алгоритмы, реализация которых более эффективна на статических данных.

Типичные ошибки при работе с динамической памятью

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

1) Попытка воспользоваться неинициализированным указателем.

Если pi – глобальная переменная , то она автоматически инициализируется нулевым значением, т.е. имеет значение NULL . Разыменование нулевого указателя приводит к ошибке времени выполнения. Если pi – локальная переменная , то она по умолчанию не инициализируется, а поэтому содержит непредсказуемое значение . Это значение трактуется как адрес вещественной переменной, к которой осуществляется доступ . По чистой случайности может оказаться, что указатель pi содержит истинный адрес переменной программы, тогда значение переменной будет изменено, выполнение программы продолжится дальше, а факт изменения переменной непредсказуемым образом повлияет на дальнейшее выполнение программы.

2) "Висячие" указатели.

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

Если после delete p; сразу написать p=NULL; , то в дальнейшем при попытке разыменовать нулевой указатель p возникнет исключение , что является более предпочтительным, чем скрытая ошибка изменения другой переменной. Данный прием следует иметь ввиду и после освобождения динамической переменной обнулять указатель :

3) "Утечка" памяти.

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

Пример. Повторное выделение памяти.

Если выделить память повторно для того же указателя, то ранее выделенная память "утечет":

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

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

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

Вызов операции delete для неинициализированного указателя игнорируется, не приводя к генерации ошибки.

5) Попытка освободить нединамическую память.

При вызове delete для нединамической переменной будет сгенерирована ошибка.

Проверка на выделение памяти

Существует единственное числовое значение , которое можно присвоить непосредственно указателю – это NULL . Нулевой адрес – особый, по этому адресу не может храниться ни одна переменная . То есть указатель , имеющий нулевое значение указывает в "никуда", к такому указателю нельзя применить оператор разыменования .

Все рассмотренные функции по работе с динамической памятью могут выделять память размером не более одного сегмента, то есть не более 64K в 16-ти разрядных моделях и не более 4G в 32-х разрядных моделях памяти.

Многомерные динамические массивы

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

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

Для выделения памяти на куче в си используется функция 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

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

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

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

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

Ошибка сегментации (Segmentation fault)

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

Пример

void foo(int *pointer)
*pointer = 0; //потенциальный Segmentation fault
>

int main()
int *p;
int x;
*NULL = 10; //совсем очевидный Segmentation fault
*p = 10; //достаточно очевидный Segmentation fault
foo(NULL); //скрытый Segmentation fault
scanf("%d", x); //скрытый и очень популярный у новичков на Си Segmentation fault

Утечка памяти (Memory leak)

Если процесс попросил у ОС память, а затем про нее забыл и более не использует, это называется утечкой памяти.

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

Пример

void swap_arrays(int *A, int *B, size_t N)
int * tmp = (int *) malloc(sizeof(int)*N); //временный массив
for(size_t i = 0; i < N; i++)
tmp [i] = A[i];
for(size_t i = 0; i < N; i++)
A[i] = B[i];
for(size_t i = 0; i < N; i++)
B[i] = tmp [i];
//выходя из функции, забыли освободить память временного массива
>

int main()
int A[10] = ;
int B[10] = ;
swap_arrays(A, B, 10); //функция swap_arrays() имеет утечку памяти

int *p;
for(int i = 0; i < 10; i++) p = (int *)malloc(sizeof(int)); //выделение памяти в цикле 10 раз
*p = 0;
>
free(p); //а освобождение вне цикла - однократное. Утечка!

Как избежать ошибок работы с динамической памятью?

  1. Во-первых, быть аккуратным и внимательным.
  2. Во-вторых, если память выделена на одном уровне, освобождение должно быть совершено на том же уровне. Например, если функция выделила память, она же должна ее освободить перед выходом.
    В исключительных ситуациях могут существовать "порождающие" функции, но их нужно "знать в лицо", их имена должны говорить об этом. С этой точки зрения пример плохой порождающий функции: стандартная функция Си создания дубликата строки strdup(). По ее имени не очевидно, что при этом выделяется динамическая память, которая обязательно должна быть освобождена.
  3. В-третьих, существуют специальные программные средства, которые позволяют искать утечки памяти, например Valgrind.

Работа с динамической памятью в Си и С++ различается.

Хотя в С++ также в конечном счете используются системные вызовы по выделению и освобождению памяти, они "обернуты" в операторы new и delete. В С++ не рекомендуется использовать механизм malloc() и free() без насущной необходимости обратной совместимости.

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