Почему malloc не работает иногда?

Я переношу проект C из Linux в Windows. В Linux он полностью стабилен. В Windows это работает очень часто, но иногда я получаю ошибку сегментации.

Я использую Microsoft Visual Studio 2010 для компиляции и отладки и, похоже, иногда мои вызовы malloc просто не выделяют память, возвращая NULL. Машина имеет свободную память; он уже прошел через этот код тысячу раз, но он все равно происходит в разных местах.

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

Есть ли что-то, что я должен быть более осторожным в Windows, чем в Linux? Что я могу делать неправильно?

Ответы

Ответ 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() с ним. И если вам нужно заменить буфер большим буфером, вам нужно освободить узел и перераспределить буфер и узел. Таким образом, есть больше работы для программиста.

Ответ 2

Другой причиной отказа malloc() в Windows является то, что ваш код выделяется в одной DLL и освобождается в другой DLL или EXE.

В отличие от Linux, в Windows DLL или EXE имеют свои собственные ссылки на библиотеки времени выполнения. Это означает, что вы можете связать свою программу с использованием 2013 CRT для DLL, скомпилированной по сравнению с CRT 2008.

Различные режимы работы могут обрабатывать кучу по-разному. Отладочные и Release CRT определенно обрабатывают кучу по-разному. Если вы malloc() в Debug и free() в Release, это будет ужасно нарушено, и это может вызвать проблемы.

Ответ 3

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

pNewNode = malloc(sizeof(myNodeStruct) + 15000);

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

Как найти эту ошибку? В вашем отладчике посмотрите, действительно ли pNewNode действителен перед вызовом malloc. он должен указывать на 0x000000 или какое-либо другое реальное местоположение (которое фактически является мусором, пока malloc не назначит выделенный сегмент памяти).

Ответ 4

Вы можете объявить свой собственный безопасный 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));