Как удалить из памяти массив с
В этом руководстве мы научимся эффективно управлять памятью в C++ с помощью операций создания и удаления на примерах. С++ позволяет нам выделять память для переменной или массива во время выполнения. Это известно как распределение динамической памяти.
В других языках программирования, таких как Java и Python, компилятор автоматически управляет памятью, выделенной для переменных. Но в C++ дело обстоит иначе. В С++ нам нужно вручную освободить динамически выделенную память после того, как мы перестали использовать переменную.
Мы можем динамически выделять, а затем освобождать память, используя операторы new и delete соответственно.
Оператор new
Оператор new выделяет память для переменной. Например:
Здесь мы динамически выделяем память для переменной типа int с помощью оператора new.
Обратите внимание, что мы использовали указатель pointVar для динамического распределения памяти. Это связано с тем, что оператор new возвращает адрес ячейки памяти.
В случае массива оператор new возвращает адрес первого элемента массива.
Из приведенного выше примера мы видим, что синтаксис использования оператора new следующий:
Оператор delete
Когда нам больше не нужно использовать переменную, которую мы объявили динамически, мы можем освободить память, занимаемую переменной.
Для этого используется оператор delete. Он возвращает память операционной системе, это и называется освобождением памяти.
Здесь мы динамически выделяем память для переменной типа int с помощью указателя pointVar .
После печати содержимого pointVar мы освободили память с помощью delete.
Примечание. Если программа использует большой объем нежелательной памяти с помощью new, система может дать сбой, поскольку для операционной системы не будет памяти. В этом случае оператор delete может помочь системе.
Пример 1: распределение динамической памяти
В этой программе мы динамически выделяли память для двух переменных типа int и float. После присвоения им значений и их печати, мы, наконец, освобождаем память с помощью кода:
Примечание. Динамическое выделение памяти в С++ может повысить эффективность управления памятью.
Особенно для массивов, где часто мы не знаем размер массива до времени выполнения.
Пример 2: для массивов
В этой программе мы попросили пользователя ввести количество студентов и сохранить его в переменной num .
Затем мы динамически выделили память для массива с плавающей запятой с помощью new .
Мы вводим данные в массив (а позже распечатываем их), используя обозначение указателя.
После того, как массив нам больше не нужен, мы освобождаем память массива с помощью кода delete [] ptr.
Обратите внимание на использование квадратных скобок [] после удаления. Мы используем их, чтобы обозначить, что освобождение памяти происходит в массиве.
Пример 3: для объектов
В этой программе мы создали класс Student с частной переменной age .
Мы инициализировали Age 12 в конструкторе по умолчанию Student() и распечатали его значение с помощью функции getAge().
В main() мы создали объект Student с помощью оператора new и используем указатель ptr, чтобы указать на его адрес.
В момент создания объекта конструктор Student() инициализирует Age равным 12.
Затем мы вызываем функцию getAge(), используя код:
Обратите внимание на оператор стрелки ->. Он используется для доступа к членам класса с помощью указателей.
Я думал, что, установив первый элемент в значение null, очистит все содержимое массива char.
однако это только устанавливает первый элемент в значение null.
вместо того, чтобы использовать memset , Я думал, что 2 примера выше должны очистить все данные.
Это зависит от того, как вы хотите просмотреть массив. Если вы просматриваете массив как ряд символов, единственный способ очистить данные-коснуться каждой записи. memset вероятно, самый эффективный способ достичь этого.
С другой стороны, если вы хотите просмотреть это как строку с нулевым завершением C/C++, установка первого байта в 0 эффективно очистит строку.
массив в C-это просто область памяти, так что действительно, my_custom_data[0] = ' '; назначение просто устанавливает первый элемент в ноль и оставляет другие элементы нетронутыми.
если вы хотите удалить все элементы массива, вам придется посетить каждый элемент. Именно это memset для:
это, как правило, самый быстрый способ позаботиться об этом. Если вы можете использовать C++, рассмотрите std:: fill вместо:
почему вы думаете, что установка одного элемента очистит весь массив? В C, особенно, мало что происходит без явного программирования программиста. Если установить первый элемент в ноль (или любое значение), то вы сделали именно это, и ничего больше.
при инициализации вы можете установить массив в ноль:
в противном случае я не знаю никакой техники, кроме memset или чего-то подобного.
попробуйте следующий код:
почему бы не использовать memset() ? Вот как это делается.
установка первого элемента оставляет остальную часть памяти нетронутой, но функции str будут обрабатывать данные как пустые.
Pls найти ниже, где я объяснил с данными в массиве после случая 1 & случай 2.
хотя установка первого аргумента в NULL сделает трюк, использование memset рекомендуется
Неа. Все, что вы делаете-это установка первого значения '\0' или 0.
Если вы работаете со строками с нулевым завершением, то в первом примере вы получите поведение, которое имитирует то, что вы ожидаете, однако память все еще установлена.
Если вы хотите очистить память без использования memset, используйте цикл for.
вы должны использовать функцию memset. Установка только первого элемента не будет работать, вам нужно установить все элементы - если нет, как вы можете установить только первый элемент в 0?
запись нулевого символа в первый символ делает именно это. Если вы рассматриваете его как строку, код, подчиняющийся символу завершения null, будет рассматривать его как нулевую строку, но это не то же самое, что очистка данных. Если вы хотите очистить данные, вам нужно будет использовать memset.
Я думал, что первый элемент to a null очистит все содержание массива char.
это неправильно, как вы обнаружили
однако это только устанавливает первый элемент имеет значение null.
вам нужно использовать memset для очистки всех данных, недостаточно установить одну из записей в null.
однако, если элемент массива значение null означает что-то особенное (например, при использовании нулевой завершающей строки), может быть достаточно установить первый элемент в значение null. Таким образом, любой пользователь массива поймет, что он пуст, даже если массив все еще включает старые символы в памяти
задайте для первого элемента значение NULL. печать массива char ничего вам не даст.
извините, если это очевидный вопрос, но ни Google, ни поиск здесь не привели меня к ответу.
есть ли способ полностью удалить массив?
Я хочу напротив of int[] array = new int[5]
вы просто должны позволить ему выйти за рамки и ждать, пока GC найдет его; что может быть не сразу (на самом деле, это почти наверняка не будет). Если у вас есть поле для долгоживущего объекта (который останется в области), вы можете установить значение null, что может помочь.
вы можете повлиять на GC, чтобы собрать раньше (не только ваш объект: все подходит), но вы должны редко если когда-нибудь сделать это. Я использую его только в тестовых установках; но:
дополнительные on GC.Collect :
нет необходимости удалять массив, GC будет иметь дело с ним.
когда у вас закончится память, сборщик мусора начнет работать, остановит ваш код и пересечет все живые объекты в памяти.
live означает некоторую ссылку на стек, регистр, статическую ссылку и некоторые другие вещи, известные как "корни GC". Проходя по ним, он замечает, что они живы.
Если Ваш массив больше не живет там ничто не может получить к нему доступ (это то, что определяет живость), поэтому занимаемая им память будет доступна для повторного использования.
здесь мая будет причиной назначить ссылку на null, если она будет содержать ссылки дольше, чем требуется, или она содержит большой кусок памяти, который вам нужен немедленно, но это может легко привести к обратному результату и фактически сделать массив жить дольше. Переменные экземпляра, а не стека являются лучшими кандидатами для этой оптимизации, если содержащий экземпляр будет иметь значительно более длительный срок службы, чем массив, который он содержит.
чтобы быть ясным, как новичок, вы не должны рассматривать такие вещи, пусть GC делает свою работу и только пытается помочь ей, когда вы:
- знаю, что тебе нужно
- умею
- знайте, как проверить, что вы сделали это правильно
следует читать статья Криса Брумма на эту тему; первые несколько абзацев должны объяснить ситуацию.
и любой, кто предлагает "назначить null переменной", должен обратить особое внимание на часть, которая говорит:
даже если вы добавите "aC = null;" после его использования, JIT может считать это назначение мертвым кодом и устранить его.
самое близкое, что вы получаете, - это просто ждать, пока он выйдет из области или установит его в null.
Он не будет немедленно удалять или удалять массив, но после удаления всех ссылок на массив сборщик мусора (GC) удалит его вовремя.
вы можете объявить и инициализировать свой массив в операторе if (true) <>, из оператора if (после него) переменная массива недоступна, поэтому она будет удалена.
В C++ можно использовать удалить ключевое слово. И это было первоначально упомянуто в этом комментарии и в других комментариях. История редактирования показывает это. Это жизнеспособный (хотя и побочный) ответ, даже если другие не понимают, почему. Я говорю это, потому что можно использовать его, чтобы найти свое собственное элегантное решение, если они понимают более одного языка.
основная проблема, которую я вижу с этим способом, однако, заключается в том, что вы действительно не хотите тратить тонну памяти, а затем все остановилось, когда GC запущен, потому что ОС freaking. Я храню эту мысль., учитывая, что C Sharp теперь является основным языком и используется во многих различных приложениях, включая написание игр. Было бы действительно хорошо, если бы C Sharp не действовал аналогично ActionScript/FLASH в этом отношении.
вы можете прочитать в интернете, как сделать все это. Надеюсь, это хорошая идея.
Я пробовал это дома, и это сработало отлично для меня, так почему бы не для других тоже!?
возможно, это было решено, но вы можете рассматривать массив как стек. Петля через него и поп верхней части каждой итерации.
таким образом, вы можете просто ввести больше значений в массив без необходимости его повторного создания
Статическое выделение памяти выполняется для статических и глобальных переменных. Память выделяется один раз (при запуске программы) и сохраняется на протяжении работы всей программы.
Автоматическое выделение памяти выполняется для параметров функции и локальных переменных. Память выделяется при входе в блок, в котором находятся эти переменные, и удаляется при выходе из него.
Динамическое выделение памяти является темой этого урока.
Динамическое выделение переменных
Как статическое, так и автоматическое распределение памяти имеют два общих свойства:
Размер переменной/массива должен быть известен во время компиляции.
Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).
В большинстве случаев с этим всё ОК. Однако, когда дело доходит до работы с пользовательским вводом, то эти ограничения могут привести к проблемам.
Например, при использовании строки для хранения имени пользователя, мы не знаем наперед насколько длинным оно будет, пока пользователь его не введет. Или нам нужно создать игру с непостоянным количеством монстров (во время игры одни монстры умирают, другие появляются, пытаясь, таким образом, убить игрока).
Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:
char name [ 30 ] ; // будем надеяться, что пользователь введет имя длиной менее 30 символов! Polygon rendering [ 40000 ] ; // этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов!Это плохое решение, по крайней мере, по трем причинам:
Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив rendering : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!
В Visual Studio это можно проверить, запустив следующий фрагмент кода:
int array [ 1000000000 ] ; // выделяем 1 миллиард целочисленных значенийЛимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.
Для динамического выделения памяти одной переменной используется оператор new:
new int ; // динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем)В примере, приведенном выше, мы запрашиваем выделение памяти для целочисленной переменной из операционной системы. Оператор new возвращает указатель, содержащий адрес выделенной памяти.
Для доступа к выделенной памяти создается указатель:
int * ptr = new int ; // динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к нейЗатем мы можем разыменовать указатель для получения значения:
* ptr = 8 ; // присваиваем значение 8 только что выделенной памятиВот один из случаев, когда указатели полезны. Без указателя с адресом на только что выделенную память у нас не было бы способа получить доступ к ней.
Как работает динамическое выделение памяти?
На вашем компьютере имеется память (возможно, большая её часть), которая доступна для использования программами. При запуске программы ваша операционная система загружает эту программу в некоторую часть этой памяти. И эта память, используемая вашей программой, разделена на несколько частей, каждая из которых выполняет определенную задачу. Одна часть содержит ваш код, другая используется для выполнения обычных операций (отслеживание вызываемых функций, создание и уничтожение глобальных и локальных переменных и т.д.). Мы поговорим об этом чуть позже. Тем не менее, большая часть доступной памяти компьютера просто находится в ожидании запросов на выделение от программ.
Когда вы динамически выделяете память, то вы просите операционную систему зарезервировать часть этой памяти для использования вашей программой. Если ОС может выполнить этот запрос, то возвращается адрес этой памяти обратно в вашу программу. С этого момента и в дальнейшем ваша программа сможет использовать эту память, как только пожелает. Когда вы уже выполнили с этой памятью всё, что было необходимо, то её нужно вернуть обратно в операционную систему, для распределения между другими запросами.
В отличие от статического или автоматического выделения памяти, программа самостоятельно отвечает за запрос и обратный возврат динамически выделенной памяти.
Освобождение памяти
Когда вы динамически выделяете переменную, то вы также можете её инициализировать посредством прямой инициализации или uniform-инициализации (в С++11):
int * ptr1 = new int ( 7 ) ; // используем прямую инициализацию int * ptr2 = new int < 8 >; // используем uniform-инициализациюКогда уже всё, что требовалось, выполнено с динамически выделенной переменной — нужно явно указать для С++ освободить эту память. Для переменных это выполняется с помощью оператора delete:
// Предположим, что ptr ранее уже был выделен с помощью оператора new delete ptr ; // возвращаем память, на которую указывал ptr, обратно в операционную систему ptr = 0 ; // делаем ptr нулевым указателем (используйте nullptr вместо 0 в C++11)Оператор delete на самом деле ничего не удаляет. Он просто возвращает память, которая была выделена ранее, обратно в операционную систему. Затем операционная система может переназначить эту память другому приложению (или этому же снова).
Хотя может показаться, что мы удаляем переменную, но это не так! Переменная-указатель по-прежнему имеет ту же область видимости, что и раньше, и ей можно присвоить новое значение, как и любой другой переменной.
Обратите внимание, удаление указателя, не указывающего на динамически выделенную память, может привести к проблемам.
Висячие указатели
Язык C++ не предоставляет никаких гарантий относительно того, что произойдет с содержимым освобожденной памяти или со значением удаляемого указателя. В большинстве случаев, память, возвращаемая операционной системе, будет содержать те же значения, которые были у нее до освобождения, а указатель так и останется указывать на только что освобожденную (удаленную) память.
Указатель, указывающий на освобожденную память, называется висячим указателем. Разыменование или удаление висячего указателя приведет к неожиданным результатам. Рассмотрим следующую программу:
int * ptr = new int ; // динамически выделяем целочисленную переменную * ptr = 8 ; // помещаем значение в выделенную ячейку памяти delete ptr ; // возвращаем память обратно в операционную систему, ptr теперь является висячим указателем std :: cout << * ptr ; // разыменование висячего указателя приведет к неожиданным результатам delete ptr ; // попытка освободить память снова приведет к неожиданным результатам такжеВ программе, приведенной выше, значение 8 , которое ранее было присвоено динамической переменной, после освобождения может и далее находиться там, а может и нет. Также возможно, что освобожденная память уже могла быть выделена другому приложению (или для собственного использования операционной системы), и попытка доступа к ней приведет к тому, что операционная система автоматически прекратит выполнение вашей программы.
Процесс освобождения памяти может также привести и к созданию нескольких висячих указателей. Рассмотрим следующий пример:
int * ptr = new int ; // динамически выделяем целочисленную переменную int * otherPtr = ptr ; // otherPtr теперь указывает на ту же самую выделенную память, что и ptr delete ptr ; // возвращаем память обратно в операционную систему. ptr и otherPtr теперь висячие указатели // Однако, otherPtr по-прежнему является висячим указателем!Есть несколько рекомендаций, которые могут здесь помочь:
Во-первых, старайтесь избегать ситуаций, когда несколько указателей указывают на одну и ту же часть выделенной памяти. Если это невозможно, то выясните, какой указатель из всех «владеет» памятью (и отвечает за её удаление), а какие указатели просто получают доступ к ней.
Правило: Присваивайте удаленным указателям значение 0 (или nullptr в C++11), если они не выходят из области видимости сразу же после удаления.
Оператор new
При запросе памяти из операционной системы в редких случаях она может быть не выделена (т.е. её может и не быть в наличии).
По умолчанию, если оператор new не сработал, память не выделилась, то генерируется исключение bad_alloc . Если это исключение будет неправильно обработано (а именно так и будет, поскольку мы еще не рассматривали исключения и их обработку), то программа просто прекратит свое выполнение (произойдет сбой) с ошибкой необработанного исключения.
Во многих случаях процесс генерации исключения оператором new (как и сбой программы) нежелателен, поэтому есть альтернативная форма оператора new, которая возвращает нулевой указатель, если память не может быть выделена. Нужно просто добавить константу std::nothrow между ключевым словом new и типом данных:
int * value = new ( std :: nothrow ) int ; // указатель value станет нулевым, если динамическое выделение целочисленной переменной не выполнитсяВ примере, приведенном выше, если оператор new не возвратит указатель с динамически выделенной памятью, то возвратится нулевой указатель.
Разыменовывать его также не рекомендуется, так как это приведет к неожиданным результатам (скорее всего, к сбою в программе). Поэтому наилучшей практикой является проверка всех запросов на выделение памяти для обеспечения того, что эти запросы будут выполнены успешно и память выделится:
int * value = new ( std :: nothrow ) int ; // запрос на выделение динамической памяти для целочисленного значения if ( ! value ) // обрабатываем случай, когда new возвращает null (т.е. память не выделяется)Поскольку не выделение памяти оператором new происходит крайне редко, то обычно программисты забывают выполнять эту проверку!
Нулевые указатели и динамическое выделение памяти
Нулевые указатели (указатели со значением 0 или nullptr ) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:
// Если для ptr до сих пор не выделено памяти, то выделяем еёУдаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:
Вместо этого вы можете просто написать:
Если ptr не является нулевым, то динамически выделенная переменная будет удалена. Если значением указателя является нуль, то ничего не произойдет.
Утечка памяти
Динамически выделенная память не имеет области видимости, т.е. она остается выделенной до тех пор, пока не будет явно освобождена или пока ваша программа не завершит свое выполнение (и операционная система очистит все буфера памяти самостоятельно). Однако указатели, используемые для хранения динамически выделенных адресов памяти, следуют правилам области видимости обычных переменных. Это несоответствие может вызвать интересное поведение, например:
Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через использование оператора delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит свое выполнение, ptr выйдет из области видимости. Поскольку ptr — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда ptr уничтожится, больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И в результате эту динамически выделенную целочисленную переменную нельзя будет удалить.
Это называется утечкой памяти. Утечка памяти происходит, когда ваша программа теряет адрес некоторой динамически выделенной части памяти (например, переменной или массива), прежде чем вернуть её обратно в операционную систему. Когда это происходит, то программа уже не может удалить эту динамически выделенную память, поскольку больше не знает, где выделенная память находится. Операционная система также не может использовать эту память, поскольку считается, что она по-прежнему используется вашей программой.
Хотя утечка памяти может возникнуть и из-за того, что указатель выходит из области видимости, возможны и другие способы, которые могут привести к утечкам памяти. Например, если указателю, хранящему адрес динамически выделенной памяти, присвоить другое значение:
Читайте также: