Подтвердить что ты не робот

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

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

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

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

Спасибо.

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

Нет. Вы получаете поведение undefined. Это означает, что все может произойти, из-за этого сбой (yay) на него "работает" (boo), переформатируя ваш жесткий диск и заполняя его текстовыми файлами, которые говорят "UB, UB, UB..." (wat).

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

Более конкретно, использование любой памяти, которую вы не выделили, - это поведение undefined. Вы получаете один байт от malloc(1), что он.

Ответ 3

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

Обратите внимание, что это определенно поведение undefined!

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

Вам просто повезло. Вы пишете места, которые у вас нет, приводят к поведению undefined.

Ответ 9

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

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

Ответ 10

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

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

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

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

Вы должны использовать новые и удалять операторы в С++... И безопасный указатель на управление этими операциями не достигает предела выделенного массива...

Ответ 14

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

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

Ответ 15

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

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

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

Ответ 16

Мой ответ в ответ на вопрос: почему printf не вызывает ошибку или производит мусор?

От

Язык программирования Си от Дениса Ритчи и Кернигана

 typedef long Align;    /* for alignment to long boundary */
   union header {         /* block header */
       struct {
           union header *ptr; /* next block if on free list */
           unsigned size;     /* size of this block */
       } s;
       Align x;           /* force alignment of blocks */
   };
   typedef union header Header;

Поле Align никогда не используется; это просто заставляет каждый заголовок выравниваться по границе наихудшего случая. В malloc запрошенный размер в символах округляется до нужного количества единиц размера заголовка; выделенный блок содержит еще одну единицу для самого header, и это значение записывается в поле size заголовка. Указатель, возвращаемый функцией malloc, указывает на свободное пространство, а не на сам заголовок.

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

   -----------------------------------------
   |        |     SIZE     |               |
   -----------------------------------------
     |        |
  points to   |-----address returned touser
   next free
   block
        -> a block returned by malloc 

В заявлении

char* test = malloc(1);

malloc() попытается найти последовательные байты из раздела кучи ОЗУ, если запрошенные байты доступны, и вернет address как показано ниже

 --------------------------------------------------------------
| free memory  | memory in size allocated for user |           |
----------------------------------------------------------------
                                                              0x100(assume address returned by malloc)
                                                              test

Поэтому, когда malloc(1) выполняется, он не выделяет только 1 байт, он выделяет несколько extra байтов для поддержки таблицы структуры/кучи выше. Вы можете узнать, сколько фактической памяти выделено, когда вы запрашивали только 1 байт, печатая test[-1] поскольку непосредственно перед тем, как этот блок содержит размер.

char* test = malloc(1);
printf("memory allocated in bytes = %d\n",test[-1]);

Ответ 17

Если переданный размер равен нулю, а ptr не равен NULL, то вызов эквивалентен свободному.