Почему malloc иногда не работает?
Я портирую проект C с Linux на Windows. На Linux это полностью стабильно. В Windows это работает хорошо в большинстве случаев, но иногда я получаю ошибку сегментации.
Я использую Microsoft Visual Studio 2010 для компиляции и отладки и, похоже, иногда мои вызовы malloc просто не выделяют память, возвращая NULL. Машина имеет свободную память; он уже проходил через этот код тысячу раз, но все же происходит в разных местах.
Как я уже сказал, это не происходит все время или в одном и том же месте; это выглядит как случайная ошибка.
Есть ли что-то, что я должен быть более осторожным в Windows, чем в Linux? Что я могу делать не так?
4 ответа
malloc()
возвращает неверный указатель NULL, когда он не может обслуживать запрос памяти. В большинстве случаев процедуры выделения памяти C управляют списком или кучей доступной памяти с помощью обращений к операционной системе, чтобы выделить дополнительные порции памяти, когда malloc()
вызов сделан, и в списке нет ни кучи, ни кучи для удовлетворения запроса.
Итак, первый случай malloc()
сбой происходит, когда запрос памяти не может быть удовлетворен, поскольку в списке или куче нет используемого блока памяти, и когда управление памятью времени выполнения 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 ()
чтобы блоки стандартного размера выделялись так, чтобы вы выделяли блоки по 16 байт, используя формулу типа ((size / 16) + 1) * 16 или более вероятно ((size >> 4) + 1) << 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()
в теме. И если вам нужно заменить буфер большим буфером, вам нужно освободить узел и перераспределить буфер и узел. Таким образом, есть больше работы для программиста.
Еще одна причина malloc()
потерпеть неудачу в Windows, если ваш код размещается в одной DLL и освобождается в другой DLL или EXE.
В отличие от Linux, в Windows DLL или EXE имеют свои собственные ссылки на библиотеки времени выполнения. Это означает, что вы можете связать свою программу, используя CRT 2013 с DLL, скомпилированной с CRT 2008.
Различные среды выполнения могут обрабатывать кучу по-разному. CRT Debug и Release определенно обрабатывают кучу по-разному. если ты malloc()
в Debug и free()
в Release это ужасно сломается, и это может стать причиной вашей проблемы.
Я видел случаи, когда malloc не работает, потому что сам указатель, который будет указывать на новую память, сам не выделен:
pNewNode = malloc(sizeof(myNodeStruct) + 15000);
Если по какой-либо причине pNewNode необходимо было предварительно создать или выделить, он недействителен, и malloc завершится ошибкой, поскольку результат выделения malloc (который сам по себе успешен) не может быть сохранен в указателе. Когда эта ошибка присутствует, я видел, что при запуске одной и той же программы несколько раз код будет работать в некоторых случаях (когда указатель присутствует случайно, но просто по счастливой случайности), но во многих случаях он никуда не укажет, поскольку это никогда не было выделено.
Как найти эту ошибку? В вашем отладчике посмотрите, действительно ли pNewNode действительно перед вызовом malloc. он должен указывать на 0x000000 или другое реальное местоположение (которое фактически является мусором, пока malloc не назначит фактический выделенный сегмент памяти).
Вы можете объявить свой собственный безопасный malloc на основе рекурсивной функции:
void *malloc_safe(size_t size)
{
void* ptr = malloc(size);
if(ptr == NULL)
return malloc_safe(size);
else
return ptr;
}
В случае сбоя malloc эта функция снова вызывается и пытается выделить память, пока ptr становится!= NULL.
с помощью:
int *some_ptr = (int *)malloc_safe(sizeof(int));