Почему Мэллок иногда не работает?

Я портирую проект 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 байт, используя формулу ((размер / 16) + 1) * 16 или более вероятно ((размер >> 4) + 1) 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.

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


Я видел случаи, когда 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 становится != НЕДЕЙСТВИТЕЛЬНЫЙ.

использование:

int *some_ptr = (int *)malloc_safe(sizeof(int));