Почему вы указываете размер при использовании malloc в C?

Возьмите следующий код:

int *p = malloc(2 * sizeof *p);

p[0] = 10;  //Using the two spaces I
p[1] = 20;  //allocated with malloc before.

p[2] = 30;  //Using another space that I didn't allocate for. 

printf("%d", *(p+1)); //Correctly prints 20
printf("%d", *(p+2)); //Also, correctly prints 30
                      //although I didn't allocate space for it

Строкой malloc(2 * sizeof *p) я выделяю место для двух целых чисел, верно? Но если я добавлю int в третью позицию, я все равно буду распределяться правильно и извлекаться.

Итак, у меня вопрос: почему вы указываете размер, когда используете malloc?


person Andreas Grech    schedule 06.08.2009    source источник
comment
Кажется, вы выбрали p [3], потому что оно больше 2 и, следовательно, вне диапазона. Вы правы, но помните, что вы должны мыслить с нулевым индексом, поэтому на самом деле даже p [2] выходит за пределы допустимого диапазона. Если вы выделили место для двух целых чисел, вы можете получить их, используя * p (или p [0]) и * (p + 1) (или p [1]), а не p [1] и p [2] .   -  person MatrixFrog    schedule 07.08.2009
comment
Вы правы, глупая ошибка с моей стороны. Я отредактирую вопрос, чтобы не путать первоначальный замысел   -  person Andreas Grech    schedule 07.08.2009
comment
Вдобавок ко всем ответам, указывающим на проблему, у вас есть гарантированный segfault в ваших руках, если malloc вернул NULL, что ему разрешено. Всегда проверяйте возвращаемое значение malloc перед его использованием. Если он ПУСТО (NULL), диспетчер памяти отказывается выделить вам больше памяти (обычно потому, что вы просили слишком много или израсходовали все, что он готов вам дать).   -  person Bob Somers    schedule 07.08.2009
comment
Ха, боже мой, что со всеми отрицательными голосами? я получаю два голоса за, а затем 2 голоса против   -  person Andreas Grech    schedule 07.08.2009
comment
@stepancheg - уточните, почему?   -  person Andreas Grech    schedule 07.08.2009
comment
о, да ладно, тебе нужно немного уточнить свою критику; ты не можешь просто сказать мне, что я глуп, и оставить все как есть. Скажите, а что такого плохого в моем вопросе?   -  person Andreas Grech    schedule 07.08.2009
comment
Не беспокойся об этом. Это не глупый вопрос, это вопрос, связанный с концепциями низкоуровневого программирования, которым я новичок. Степанчегу нужно научиться не кусать новичков.   -  person Tyler McHenry    schedule 07.08.2009
comment
да, ну, я новичок в программировании низкого уровня ... только начал изучать C на прошлой неделе, на самом деле хех ... после пары лет изучения C # и других вещей высокого уровня   -  person Andreas Grech    schedule 07.08.2009
comment
Не похоже, чтобы кто-то нашел время, чтобы объяснить, почему malloc скрывает для вас это лишнее пространство. Malloc необходимо выделить запрошенное пространство + немного больше для хранения собственных метаданных о выделенной памяти. Распространенная проблема с дикими записями в память malloc - это засорение метаданных malloc, что приводит к сбою последующих free и malloc. Кроме того, в зависимости от реализации, malloc может просто запросить у ОС большую часть памяти, чтобы не нужно было снова запрашивать ОС, когда вы вызываете другой malloc (это упрощено, но я надеюсь, что это объясняет).   -  person Falaina    schedule 07.08.2009
comment
@ Фалаина, это не совсем так. Malloc не обязательно выделяет рассматриваемое дополнительное пространство. Да, malloc работает так, но он может помещать метаданные перед указателем, который он вам дает, или где-то еще. Тот факт, что p [3] работает, не означает, что вы нашли метаданные malloc. Это может быть хранилище другой переменной или полностью неиспользуемая часть пространства виртуальной памяти.   -  person Tyler McHenry    schedule 07.08.2009
comment
@Tyler McHenry, очень хороший аргумент, однако я пытался проиллюстрировать некоторые общие возможности в своем комментарии (вот почему я использовал термин «дикая запись» в память, а не «списать конец»). В конкретном примере этого OP он может фактически не перезаписывать какие-либо метаданные, я просто предлагал возможность :)   -  person Falaina    schedule 07.08.2009


Ответы (17)


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

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

По таким вопросам, как этот, полезной ссылкой на проконсультируйтесь. См. 7.3b.

По теме (юмористической) см. Также список bloopers от ART.

person Sinan Ünür    schedule 06.08.2009
comment
Идеальная метафора. Вы можете нелегально припарковаться изо дня в день и, может быть, ничего плохого не случится. Но все же есть вероятность того, что вас заберут или заберут билет, поэтому вам не следует этого делать. - person MatrixFrog; 07.08.2009
comment
Вы даете хорошее объяснение, вы получаете положительные голоса. Спасибо за объяснение. - person David Thornley; 07.08.2009
comment
Аналогия хороша, но вы не объясняете, что происходит. Людям, которые понимают проблему, это кажется очевидным ... но если бы он понимал проблему, он бы не задавал вопрос. Тот факт, что программа может выйти из строя или память, которую он использует, может быть перезаписана, не очевиден. Объясните, что происходит за этой аналогией, и это простой +1. - person Beska; 07.08.2009
comment
+1. Отличная аналогия. И потраченное впустую время ^ H ^ H ^ H ^ H ^ H, потраченное на поиск повреждений памяти, - это штраф за преступление. - person Andrew Y; 07.08.2009
comment
@ Андрей: Если повезет. Снова в школу, мистер Бин предлагает более реалистичный вид. Когда мистер Бин выталкивает машину из стоянки, обнесенной тросом, и сам пользуется ею, он не получает билет. Вместо этого запланированная армия демонстрация способности танка управлять чем-либо, используя машину в этом пространстве, проходит, как и планировалось, с использованием машины мистера Бина, а не той, которую он вытеснил. - person supercat; 20.04.2012

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

Отказ от ответственности: мое последнее настоящее программирование на C было выполнено около 15 лет назад.

person Igal Serban    schedule 06.08.2009
comment
Никаких заявлений об отказе от ответственности не требуется, это абсолютно правильный ответ. Обычно следующая переменная, которую вы выделяете, перезаписывается, но если вам действительно не повезло, вы можете попасть в пространство переменных другой программы и испортить что-то случайное. - person Ricket; 07.08.2009
comment
Под этим я имел в виду, если после объявления p вы также объявили int * q = malloc (sizeof (int)); (массив с одним элементом), вероятно (но не гарантируется), что p [2] == q [0]. Это также вводит случаи, когда ваша программа может продолжаться, и она не может нанести ущерб, а затем внезапно возникает случай, когда p [2]! = Q [0] и ошибка возникает один раз ... Эти приходят и уходят , непредсказуемые ошибки чрезвычайно сложно отлаживать. - person Ricket; 07.08.2009

Приведу аналогию, почему это «работает».

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

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

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

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

Теперь отсутствует часть рисунка. Фрагмент, который вы нарисовали на бумаге другого человека.

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

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

C построен как бензопила на стероидах. Вы почти ничего не можете сделать. Это также означает, что вам нужно знать, что вы делаете, иначе вы пропилите дерево насквозь себе под ногу, прежде чем осознаете это.

person Lasse V. Karlsen    schedule 06.08.2009
comment
Это превосходная аналогия, поскольку она также отвечает на кое-что из того, почему для этого ничего не ловится; можно построить рамку вокруг листа бумаги так, чтобы ручка не могла выходить за ее пределы, но это определенно будет больше работы, чем просто взять лист бумаги и нарисовать. В некоторых условиях считается, что дополнительная стоимость рамы оправдана. В других средах люди с подвесками считаются достаточно надежными, и им не нужно возиться с фреймами. - person supercat; 21.11.2011

Тебе (не) повезло. Доступ к p [3] не определен, поскольку вы не выделили эту память для себя. Чтение / списание конца массива - один из способов загадочного сбоя программ на языке C.

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

Что еще хуже, вы можете перезаписать некоторые другие данные и не заметить. Представьте, что это случайно перезаписывает сумму денег, которую вы кому-то должны ;-)

person Harold L    schedule 06.08.2009
comment
Это также может привести к перезаписи информации о том, что находится в куче, а это означает, что malloc () и free () могут делать все более неприятные вещи, пока не произойдет очень загадочный сбой без видимой причины. - person David Thornley; 07.08.2009

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

Таким образом, malloc должен знать размер памяти, который вам нужен, потому что он не знает, что вы в конечном итоге будете делать с памятью, сколько объектов вы планируете записать в память и т. Д.

person Adam Batkin    schedule 06.08.2009
comment
Я бы сказал, что это вообще-то не повезло :) - person bdonlan; 06.08.2009
comment
Вы видите, как не повезло, когда код запускается в какой-то системе, где куча ведет себя иначе (но, конечно, в рамках стандарта). Он работал на моей машине - это не та фраза, которую клиенты хотят слышать. - person sharptooth; 12.08.2009

Все это восходит к тому, что C позволил вам выстрелить себе в ногу. То, что вы можете это сделать, не означает, что вы должны это делать. Определенно не гарантируется, что значение p + 3 будет тем, что вы поместили туда, если вы специально не распределили его с помощью malloc.

person Matt Kellogg    schedule 06.08.2009

Попробуй это:

int main ( int argc, char *argv[] ) {
  int *p = malloc(2 * sizeof *p);
  int *q = malloc(sizeof *q);
  *q = 100;

  p[0] = 10;    p[1] = 20;    p[2] = 30;    p[3] = 40;
  p[4] = 50;    p[5] = 60;    p[6] = 70;


  printf("%d\n", *q);

  return 0;
}

На моей машине он печатает:

50

Это потому, что вы перезаписали память, выделенную для p, и наступили на q.

Обратите внимание, что malloc может не помещать p и q в непрерывную память из-за ограничений выравнивания.

person user47559    schedule 06.08.2009

Память представлена ​​в виде перечисляемой непрерывной линии слотов, в которых могут храниться числа. Функция malloc использует некоторые из этих слотов для своей собственной информации отслеживания, а также иногда возвращает слоты большего размера, чем вам нужно, так что когда вы вернете их позже он не застрял в непривычно маленьком куске памяти. Ваш третий int либо приземляется на собственные данные mallocs, либо на оставшееся пустое пространство в возвращаемом фрагменте, либо в области ожидающей памяти, которую malloc запросил у ОС, но еще не распределен для вас.

person Michael Speer    schedule 06.08.2009

В зависимости от платформы p [500], вероятно, тоже будет "работать".

person Sanjaya R    schedule 06.08.2009

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

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

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

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

Изменить: OP исправил побочную проблему с *(p+2), противоречащим p[1], поэтому я отредактировал свой ответ, чтобы убрать эту точку.

person RBerteig    schedule 06.08.2009

Вы просите место для двух целых чисел. p [3] предполагает, что у вас есть место для 4 целых чисел!

===================

Вам нужно указать malloc, сколько вам нужно, потому что он не может угадать, сколько памяти вам нужно.

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

Это как просить место в ресторане. Вам могут дать стол побольше, чем вам нужно. Или вам могут предоставить место за столом с другими людьми. Или вам могут предоставить стол на одно место. Маллок волен делать все, что захочет, пока вы получаете свое единственное место.

В рамках «контракта» на использование malloc вы обязаны никогда не ссылаться на память сверх того, что вы запросили, потому что вы гарантированно получите только запрошенную сумму.

person Community    schedule 06.08.2009

Поскольку malloc () выделяется в БАЙТАХ. Итак, если вы хотите выделить (например) 2 целых числа, вы должны указать размер в байтах 2 целых чисел. Размер целого числа можно найти с помощью sizeof (int), поэтому размер в байтах 2 целых чисел равен 2 * sizeof (int). Сложите все это вместе, и вы получите:

int * p = malloc(2 * sizeof(int));

Примечание: учитывая, что вышеупомянутое выделяет место только для ДВУХ целых чисел, вы очень непослушны, назначая третье. Тебе повезло, что он не падает. :)

person horseyguy    schedule 06.08.2009
comment
фактически, int *p = malloc(2 * sizeof *p); также назначает правильную сумму, потому что она умножается на размер указателя, который в данном случае является int - person Andreas Grech; 07.08.2009
comment
@Dreas: Это берет sizeof тип, на который указывает p, а не сам p. Обычно, учитывая in *p, sizeof(int) == sizeof(*p). - person Phil Miller; 07.08.2009

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

person insitu    schedule 06.08.2009

Когда вы используете * (p + 3), вы обращаетесь за пределы даже с использованием 2 * sizeof (* p), следовательно, вы получаете доступ к недопустимому блоку памяти, идеально подходящему для ошибок seg.

Вы указываете размер b / c, иначе функция не знает, какой размер блока из кучи памяти выделить вашей программе для этого указателя.

person stanigator    schedule 06.08.2009
comment
Это неправильно, sizeof (* p) - это размер типа, на который указывает p. Мне нравится sizeof (* p) больше, чем sizeof (int), потому что последний делает более подверженным ошибкам изменение типа. - person Ville Laurikari; 06.08.2009

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

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

В-третьих, по всей вероятности, функция malloc () в первый раз, вероятно, не просто выделяет вашему процессу 8 байтов. Это зависит от реализации, но более вероятно, что диспетчер памяти выделит полную страницу для вашего использования только потому, что легче выделять фрагменты размера страницы .... тогда последующие malloc () будут дополнительно разделять предыдущий malloc ( ) ed page.

person KFro    schedule 06.08.2009
comment
C не позволяет вам писать в любую часть памяти. C гарантирует, что вы можете писать в определенные части, а все остальное - неопределенное поведение. Это означает, что все, что делает реализация, является совершенно законным C, включая форматирование вашего жесткого диска, отправку всем в вашем списке контактов рекламных материалов от Amway или даже выполнение того, что вы ожидали. Только не рассчитывай на последнее. - person David Thornley; 07.08.2009
comment
Я предполагаю, что моя точка зрения состоит в том, что неопределенное поведение не является неопределенным. Это определяется реализацией. Есть много программ C, которые используют тот факт, что запись в определенные места в памяти делает что-то определенное из-за того, что реализация позволяет это. Например, регистры специального назначения могут управлять аппаратной функцией определенным образом ... и мы понимаем, что в то время, когда мы пишем код C. - person KFro; 08.08.2009

Как все уже говорили, вы пишете в память, которая на самом деле не выделена, а это означает, что что-то может произойти, чтобы перезаписать ваши данные. Чтобы продемонстрировать проблему, вы можете попробовать что-то вроде этого:

int *p = malloc(2 * sizeof(int));
p[0] = 10; p[1] = 20; p[2] = 30;
int *q = malloc(2 * sizeof(int));
q[0] = 0; // This may or may not get written to p[2], overwriting your 30.

printf("%d", p[0]); // Correctly prints 10
printf("%d", p[1]); // Correctly prints 20
printf("%d", p[2]); // May print 30, or 0, or possibly something else entirely.

Невозможно гарантировать, что ваша программа выделит место для q в точке p [2]. На самом деле он может выбрать совершенно другое место. Но для такой простой программы это кажется вероятным, и если она действительно выделяет q в том месте, где будет p [2], это ясно продемонстрирует ошибку вне допустимого диапазона.

person MatrixFrog    schedule 06.08.2009

person    schedule
comment
sizeof(*p): размер объекта, на который указывает p, а не размер p. - person Sinan Ünür; 07.08.2009
comment
Нет, потому что тип данных не всегда является целым числом. В данном случае это указатель, но вам нужно выделить место для типа данных, который может быть произвольным, скажем, для структуры размером 40 байт или около того. Другими словами, ‹type› * p = malloc (2 * sizeof (‹type›)) - person Yuriy Y. Yermilov; 10.08.2009
comment
Символ * на p разыменовывает указатель, который заставляет его оценивать sizeof (int). Если бы p был определен как double, вы бы получили sizeof (* p) == sizeof (double). - person Matthew Iselin; 10.08.2009
comment
Если вы дадите sizeof выражение, оценивающее значение 9 (в отличие от типа), оно будет принимать размер типа этого значения. Следовательно, для всех типов X выполняется X *p; sizeof(*p) == sizeof(X);. - person Logan Capaldo; 10.08.2009
comment
Эээ, это не удалось правильно отформатировать ... Но суть все еще ясна. - person Matthew Iselin; 10.08.2009