Я могу использовать больше памяти, чем, сколько я выделил с помощью malloc (), почему?

char *cp = (char *) malloc(1);
strcpy(cp, "123456789");
puts(cp);

выводим «123456789» как на gcc (Linux), так и на Visual C ++ Экспресс, означает ли это, что когда есть свободная память, я могу использовать больше, чем я выделил с помощью malloc () ?

и почему malloc (0) не делает ' t вызывает ошибку во время выполнения?

Спасибо.

34
задан woongiap 18 August 2010 в 09:10
поделиться

15 ответов

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

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

malloc - это не функция операционной системы, а вызов библиотеки C. Это можно реализовать разными способами. Вероятно, ваш вызов malloc привел к запросу страницы из операционной системы. Тогда malloc решил дать вам указатель на единственный байт внутри этой страницы. Когда вы записывали в память из указанного вам места, вы просто записывали "страницу", которую операционная система предоставила вашей программе, и, таким образом, операционная система не увидит никаких неправильных действий.

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

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

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

PS, когда я говорю «операционная система» выше, я действительно имею в виду «операционная система в сочетании с привилегированным доступом к ЦП». ЦП и MMU (блок управления памятью) запускают определенные прерывания или обратные вызовы в операционной системе, если процесс пытается использовать страницу, которая не была выделена этому процессу. Затем операционная система полностью завершает работу вашего приложения и позволяет системе продолжить работу. Раньше, до появления блоков управления памятью и привилегированных инструкций ЦП, вы могли писать практически в любом месте памяти в любое время - и тогда ваша система была бы полностью во власти последствий этой записи в память!

63
ответ дан 27 November 2019 в 16:02
поделиться

strcpy () не проверяет, выделена ли память, в которую выполняется запись. Он просто берет адрес назначения и записывает исходный символ за символом, пока не достигнет '\ 0'. Итак, если выделенная память назначения меньше, чем исходная, вы просто выполняете запись в памяти. Это опасная ошибка, потому что ее очень сложно отследить.

put () записывает строку, пока она не достигнет '\ 0'.

Я предполагаю, что malloc (0) возвращает только NULL и не вызывает ошибки времени выполнения.

0
ответ дан 27 November 2019 в 16:02
поделиться

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

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

0
ответ дан 27 November 2019 в 16:02
поделиться

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

0
ответ дан 27 November 2019 в 16:02
поделиться

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

1
ответ дан 27 November 2019 в 16:02
поделиться

Тебе там просто повезло. Вы пишете в места, которые вам не принадлежат, это приводит к неопределенному поведению.

1
ответ дан 27 November 2019 в 16:02
поделиться

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

2
ответ дан 27 November 2019 в 16:02
поделиться

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

3
ответ дан 27 November 2019 в 16:02
поделиться

Когда вы запрашиваете malloc для 1 байта, он, вероятно, получит 1 страницу (обычно 4 КБ) из операционной системы. Эта страница будет выделена вызывающему процессу, поэтому, пока вы не выйдете за границу страницы, у вас не будет никаких проблем.

Однако учтите, что это определенно неопределенное поведение!

Рассмотрим следующий (гипотетический) пример того, что может произойти при использовании malloc :

  1. malloc (1)
  2. Если malloc равно внутренне нехватки памяти, он запросит у операционной системы еще несколько запросов. Обычно он получает страницу. Скажем, его размер составляет 4 КБ, а адреса начинаются с 0x1000
  3. . Ваш вызов возвращается, и вы получаете адрес 0x1000 для использования. Поскольку вы запросили 1 байт, это определенное поведение , если вы используете только адрес 0x1000.
  4. Поскольку операционная система только что выделила 4 КБ памяти вашему процессу, начиная с адреса 0x1000, она не будет жаловаться, если вы читаете / записываете что-то с / по адресам 0x1000-0x1fff. Так что вы можете это сделать, но это неопределенное поведение .
  5. Допустим, вы выполняете еще один malloc (1)
  6. Теперь malloc все еще имеет немного памяти, поэтому ему не нужно запрашивать дополнительную информацию у операционной системы. Вероятно, он вернет адрес 0x1001.
  7. Если вы записали более 1 байта, используя адрес, указанный из первого malloc , у вас возникнут проблемы при использовании адреса из второго malloc , потому что вы будете перезаписать данные.

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

16
ответ дан 27 November 2019 в 16:02
поделиться

Нет. Вы получаете неопределенное поведение . Это означает, что может произойти все, что угодно: от сбоя (ура) до «работы» (бу), до переформатирования жесткого диска и заполнения его текстовыми файлами с надписью «UB, UB, UB ...» (ват).

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

Точнее говоря, использование нераспределенной памяти является неопределенным поведением. Вы получаете один байт из malloc (1) , вот и все.

21
ответ дан 27 November 2019 в 16:02
поделиться

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

0
ответ дан 27 November 2019 в 16:02
поделиться

Так много ответов, и только один дает правильное объяснение. Хотя истории о размере страницы, переполнении буфера и неопределенном поведении верны (и важны), они не дают точного ответа на исходный вопрос. Фактически, любая разумная реализация malloc будет выделять, по крайней мере, размер, необходимый для выравнивания, int или void * . Почему, потому что, если бы он выделил только 1 байт, следующий кусок памяти больше не выровнялся бы. Всегда есть какие-то бухгалтерские данные вокруг ваших выделенных блоков, эти структуры данных почти всегда выровнены до некоторого числа, кратного 4. Хотя некоторые архитектуры могут обращаться к словам по невыровненным адресам (x86), они несут за это некоторые штрафы, поэтому разработчик распределителя избегает этого. . Даже в slab-распределителях нет смысла иметь 1-байтовый пул, поскольку аллоки небольшого размера на практике встречаются редко. Таким образом, весьма вероятно, что в вашем байте malloc'd есть 4 или 8 байтов (это не значит, что вы можете использовать эту «функцию», это неправильно).

РЕДАКТИРОВАТЬ: Кроме того, большинство malloc резервируют большие куски, чем запрошено, чтобы избежать многих операций копирования при вызове realloc . В качестве теста вы можете попробовать использовать realloc в цикле с растущим размером выделения и сравнить возвращаемый указатель, вы увидите, что он изменяется только после определенного порога.

2
ответ дан 27 November 2019 в 16:02
поделиться

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

.
2
ответ дан 27 November 2019 в 16:02
поделиться

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

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

1
ответ дан 27 November 2019 в 16:02
поделиться

malloc выделяет объем памяти, который вы запрашиваете, в куче, а затем возвращает указатель на void (void *), который может быть приведен к любому желанию.

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

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

Современные языки не позволяют программисту делать запись в памяти там, где он / она не должен (по крайней мере, выполняя стандартное программирование). Вот почему Java получила большую популярность. Я предпочитаю C ++ C. Вы все еще можете наносить ущерб, используя указатели, но это менее вероятно.Вот почему Умные указатели так популярны.

Для решения подобных проблем может пригодиться отладочная версия библиотеки malloc. Вам необходимо периодически вызывать функцию проверки, чтобы определить, не повреждена ли память. Когда я интенсивно работал над C / C ++ на работе, мы использовали Rational Purify , который на практике заменяет стандартный malloc (новый в C ++) на бесплатный (delete в C ++) и может возвращать довольно точные сообщить, где программа сделала то, чего не предполагалось. Однако вы никогда не будете на 100% уверены, что в вашем коде нет ошибок. Если у вас есть состояние, которое случается крайне редко, при выполнении программы вы можете не столкнуться с этим состоянием. В конечном итоге это произойдет в производственной среде в самый загруженный день с наиболее конфиденциальными данными (согласно закону Мерфи; -)

1
ответ дан 27 November 2019 в 16:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: