Почему вы указываете размер при использовании malloc в C? - PullRequest
21 голосов
/ 06 августа 2009

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

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?

Ответы [ 17 ]

126 голосов
/ 07 августа 2009

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

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

Для вопросов, подобных этому, раздел Выделение памяти в C FAQ является полезным справочным материалом для консультации. См 7,3b .

В соответствующей (юмористической) заметке см. Также список шаров от ART .

31 голосов
/ 06 августа 2009

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

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

27 голосов
/ 07 августа 2009

Позвольте привести аналогию с тем, почему это "работает".

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

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

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

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

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

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

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

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

13 голосов
/ 06 августа 2009

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

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

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

4 голосов
/ 06 августа 2009

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

4 голосов
/ 06 августа 2009

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

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

4 голосов
/ 07 августа 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 в непрерывную память из-за ограничений выравнивания.

2 голосов
/ 07 августа 2009

В зависимости от платформы, p [500] также может «работать».

2 голосов
/ 07 августа 2009

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

1 голос
/ 07 августа 2009

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

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

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

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

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

...