Зачем выделять память? (C ++) - PullRequest
0 голосов
/ 22 июня 2011

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

int main()
{
int *p = new int[5];
p[1] = 3;
p[11118] = 9;
cout<<p[11118]<<'\n';
}

Приведенный выше код работает, выводит 9.

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

Ответы [ 7 ]

15 голосов
/ 22 июня 2011

Код выше Неопределенное поведение . Он может работать, работать некорректно, вообще не работать, зависать или заказывать пиццу через Microsoft Skype. Ты не должен полагаться на неопределенное поведение:)

11 голосов
/ 22 июня 2011

Почему необходимо выделять память? Потому что таким образом вы помечаете память как свою. Никто другой не может использовать это. Он также проверяет, есть ли на самом деле доступная память. Если ваша система имеет только 1000 байт памяти, просто выбрать 1500 байт для хранения некоторых данных - плохая идея.

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

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

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

6 голосов
/ 22 июня 2011

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

Если вам нужна проверка границ, вы можете получить ее через std :: vector :: at ().

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

Все дела.

каковы потенциальные неприятности?

Неожиданное поведение.

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

Зависит от операционной системы.

4 голосов
/ 22 июня 2011

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

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

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

Тот факт, что память стека постоянно перезаписывается при вызове и возврате функций, означает, что вы не можете поместить какие-либо постоянные данные в стек, т. Е. В локальную переменную, поскольку память для локальных переменных не сохраняется неизменной после функции возвращается. Если вашей функции нужно где-то хранить постоянные данные, она должна хранить эти данные в другом месте. Это другое расположение - так называемая heap , для которой вы вручную (также называемый «динамически») запрашиваете постоянное хранилище через malloc или new. Эта область памяти находится в другом месте и не будет перезаписана или перезаписана никем, и вы можете безопасно передавать указатель на эту память столько, сколько захотите. Единственным недостатком является то, что если вы вручную не сообщите системе, что вы сделали, она не сможет использовать память для чего-то еще, поэтому вы должны вручную очистить эту динамически распределенную память. Но необходимость в функциях для хранения постоянной информации является причиной, по которой нам нужно выделять память.

(Просто для завершения картины: локальные переменные в стеке называются «автоматически распределенными». Существует также «статическое распределение», которое происходит во время компиляции и где живут глобальные переменные. Если у вас есть глобальные char[30000], вы можете с радостью читать и писать в нее из любой точки вашей программы.)

4 голосов
/ 22 июня 2011

Это похоже на два вопроса:

  1. Почему c ++ не выполняет проверку границ?
  2. Зачем нам нужно динамическое выделение памяти?

Мои ответы:

  1. Потому что тогда это будет медленнее.Вы всегда можете написать функцию доступа, которая проверяет границы, например std :: vector :: at ().
  2. Поскольку неспособность изменить размер памяти во время выполнения может быть очень неудобной (см. Ранее FORTRAN).
1 голос
/ 22 июня 2011

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

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

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

Из-за этого сегментирования и по другим причинам полный диапазон адресов обычно не доступен для приложений до тех пор, покапопросите у ОС немного памяти для конкретной цели.Например, в linux приложения запрашивают больше памяти ядра , вызывая brk или sbrk, и запрашивают ввод-вывод в память, вызывая mmap.До тех пор, пока адрес не будет возвращен через один из этих вызовов, адрес будет неотображен , и доступ к нему вызовет сбой, обычно завершающий работу программы-нарушителя.

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

0 голосов
/ 22 июня 2011

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

Нет, современные ОС предназначены только для того, чтобы этого избежать (по соображениям безопасности).
И вам необходимо выделить память, поскольку, хотя каждый процесс имеет свое собственное пространство 4 ГБ (предоставляется Windows), все они используют один и тот же xxGBПользователь имеет на своей машине.Выделение памяти помогает операционной системе знать, каким приложениям требуется больше памяти, и отдавать ее только тем, кто в ней нуждается.
Почему моему «привет миру» понадобится такая же оперативная память, что и Crysys 2?: P

РЕДАКТИРОВАТЬ:
Хорошо, кто-то неправильно понял, что я имел в виду.Я не сказал, что все в порядке, и каждый может сделать это, и ничего не произойдет.Я только что сказал, что это не повредит внешнему процессу.Это все еще неопределенное поведение, потому что никто не знает, что находится в p + 11118, но ub не означает «он может заказать пиццу через скайп» или другие «захватывающие вещи», самое большее нарушение доступа, ничего более.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...