Ответ 1
malloc()
возвращает неверный указатель NULL, когда он не может обслуживать запрос памяти. В большинстве случаев подпрограммы выделения памяти на C управляют списком или кучей доступной памяти с помощью обращений к операционной системе, чтобы выделить дополнительные порции памяти, когда выполняется вызов malloc()
, и в списке нет ни одного блока или кучи для удовлетворения. запрос.
Таким образом, первый случай сбоя malloc()
- это когда запрос памяти не может быть удовлетворен, потому что (1) нет пригодного для использования блока памяти в списке или куче среды выполнения C и (2) когда запрошено управление памятью среды выполнения C больше памяти от операционной системы, запрос был отклонен.
Вот статья о Стратегиях распределения указателей.
В этой статье на форуме приведен пример сбоя malloc из-за фрагментации памяти.
Другая причина, по которой malloc()
может потерпеть неудачу, заключается в том, что структуры данных управления памятью были повреждены, вероятно, из-за переполнения буфера, в котором выделенная область памяти использовалась для объекта, который больше, чем размер выделенной памяти. Различные версии malloc()
могут использовать разные стратегии для управления памятью и определения того, сколько памяти предоставить, когда вызывается malloc()
. Например, malloc()
может дать вам точное количество запрошенных байтов или может дать вам больше, чем вы просили, чтобы соответствовать блоку, выделенному в границах памяти, или чтобы упростить управление памятью.
С современными операционными системами и виртуальной памятью довольно сложно исчерпать память, если вы не занимаетесь действительно большим резидентным хранилищем памяти. Однако, как упомянул пользователь Yeow_Meng в комментарии ниже, если вы выполняете арифметику для определения размера, который нужно выделить, и результатом является отрицательное число, вы можете запросить огромный объем памяти, поскольку аргумент для malloc()
указывает на объем памяти. к выделению не подписано.
Вы можете столкнуться с проблемой отрицательных размеров при выполнении арифметики с указателями, чтобы определить, сколько места необходимо для некоторых данных. Этот тип ошибки типичен для анализа текста, который выполняется для текста, который является неожиданным. Например, следующий код приведет к очень большому запросу malloc()
.
char pathText[64] = "./dir/prefix"; // a buffer of text with path using dot (.) for current dir
char *pFile = strrchr (pathText, '/'); // find last slash where the file name begins
char *pExt = strrchr (pathText, '.'); // looking for file extension
// at this point the programmer expected that
// - pFile points to the last slash in the path name
// - pExt point to the dot (.) in the file extension or NULL
// however with this data we instead have the following pointers because rather than
// an absolute path, it is a relative path
// - pFile points to the last slash in the path name
// - pExt point to the first dot (.) in the path name as there is no file extension
// the result is that rather than a non-NULL pExt value being larger than pFile,
// it is instead smaller for this specific data.
char *pNameNoExt;
if (pExt) { // this really should be if (pExt && pFile < pExt) {
// extension specified so allocate space just for the name, no extension
// allocate space for just the file name without the extension
// since pExt is less than pFile, we get a negative value which then becomes
// a really huge unsigned value.
pNameNoExt = malloc ((pExt - pFile + 1) * sizeof(char));
} else {
pNameNoExt = malloc ((strlen(pFile) + 1) * sizeof(char));
}
Хорошее управление памятью во время выполнения будет пытаться объединить освобожденные фрагменты памяти, так что многие меньшие блоки будут объединены в более крупные блоки по мере их освобождения. Такое объединение фрагментов памяти уменьшает вероятность невозможности обслуживать запрос памяти с использованием того, что уже доступно в списке, или кучи памяти, управляемой во время выполнения управления памятью C.
Чем больше вы можете просто повторно использовать уже выделенную память и чем меньше вы зависите от malloc()
и free()
, тем лучше. Если вы не выполняете malloc()
, то трудно потерпеть неудачу.
Чем больше вы можете изменить многие вызовы малого размера на malloc()
на меньшее количество больших вызовов на malloc()
, тем меньше у вас шансов фрагментировать память и расширить размер списка памяти или кучи с большим количеством небольших блоков, которые невозможно объединены, потому что они не рядом друг с другом.
Чем больше вы можете одновременно смежных блоков malloc()
и free()
, тем больше вероятность того, что во время выполнения управления памятью можно объединить блоки.
Нет правила, согласно которому вы должны выполнить malloc()
с конкретным размером объекта, аргумент размера, предоставленный для malloc()
, может быть больше, чем размер, необходимый для объекта, для которого вы выделяете память. Поэтому вы можете захотеть использовать какое-то правило для вызовов malloc ()
, чтобы блоки стандартного размера выделялись путем округления до некоторого стандартного объема памяти. Таким образом, вы можете размещать в блоках по 16 байт, используя формулу вроде ((size/16) + 1) * 16 или более вероятно ((size >> 4) + 1) & lt; & lt; 4. Многие языки сценариев используют что-то подобное, чтобы увеличить вероятность повторных обращений к malloc()
и free()
, которые могли бы сопоставить запрос со свободным блоком в списке или кучей памяти.
Вот несколько простой пример попытки уменьшить количество выделенных и освобожденных блоков. Допустим, у нас есть связанный список блоков памяти переменного размера. Таким образом, структура для узлов в связанном списке выглядит примерно так:
typedef struct __MyNodeStruct {
struct __MyNodeStruct *pNext;
unsigned char *pMegaBuffer;
} MyNodeStruct;
Может быть два способа выделить эту память для конкретного буфера и его узла. Первый - это стандартное распределение узла с последующим выделением буфера, как показано ниже.
MyNodeStruct *pNewNode = malloc(sizeof(MyNodeStruct));
if (pNewNode)
pNewNode->pMegaBuffer = malloc(15000);
Однако другим способом было бы сделать что-то вроде следующего, которое использует одно выделение памяти с арифметикой указателя, так что один malloc()
обеспечивает обе области памяти.
MyNodeStruct *pNewNode = malloc(sizeof(myNodeStruct) + 15000);
if (pNewNode)
pNewNode->pMegaBuffer = ((unsigned char *)pNewNode) + sizeof(myNodeStruct);
Однако, если вы используете этот единственный метод выделения, вам необходимо убедиться, что вы последовательны в использовании указателя pMegaBuffer
, чтобы вы случайно не сделали free()
с ним. И если вам нужно заменить буфер большим буфером, вам нужно освободить узел и перераспределить буфер и узел. Таким образом, есть больше работы для программиста.