Как обрабатывать realloc, когда он выходит из строя из-за памяти?

Вопрос говорит сам за себя, но вот пример:

typedef struct mutable_t{
    int count, max;
    void **data;
} mutable_t;


void pushMutable(mutable_t *m, void *object)
{
    if(m->count == m->max){
        m->max *= 2;
        m->data = realloc(m->data, m->max * sizeof(void*));
    }
    // how to handle oom??
    m->data[m->count++] = object;
}

Как я могу справиться с нехваткой памяти и не обнулять все мои данные?

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

8 ответов

Решение

Стандартный метод - ввести новую переменную для хранения возврата из realloc. Затем вы перезаписываете свою входную переменную только в случае успеха:

tmp = realloc(orig, newsize);
if (tmp == NULL)
{
    // could not realloc, but orig still valid
}
else
{
    orig = tmp;
}

Стратегия о том, что делать, когда realloc() Сбой зависит от вашего приложения. Вопрос слишком общий, чтобы на него можно было ответить во всех возможных случаях.

Некоторые другие заметки:

Никогда не делай:

a = realloc(a, size);

Если realloc() не удается, вы потеряете исходный указатель, и realloc() не free() оригинальная память, так что вы получите утечку памяти. Вместо этого сделайте:

tmp = realloc(a, size);
if (tmp)
    a = tmp;
else
    /* handle error */

Второе замечание, которое я хочу сделать, является второстепенным и, возможно, не столь критичным, но в любом случае полезно знать об этом: увеличение объема памяти, выделяемого фактором f это хорошо. Скажем вам malloc() Сначалаn байтов. Тогда вам нужно больше памяти, чтобы вы realloc() с размером n × f. Тогда вам нужно больше памяти, поэтому вам нужно n × f2 байта. Если ты хочешь realloc() чтобы использовать пространство из двух предыдущих блоков памяти, необходимо убедиться, что n × f2 ≤ n + n × f. Решая это уравнение, мы получаем f≤ (sqrt (5) +1) / 2 = 1.618 ( Золотое сечение). Я использую фактор 1.5 в большинстве случаев.

Это немного актуальная тема, так как по этому вопросу существует 2 основных направления.

  1. Обнаружить OOM, и с помощью функции вернуть код ошибки.
  2. Обнаружение OOM и сбой вашего процесса как можно быстрее

Лично я в лагере № 2. Ожидание для очень специальных типов приложений, ООМ является смертельным периодом. Правда, отлично написанный код может обрабатывать OOM, но мало кто понимает, как писать код, который безопасен при отсутствии памяти. Еще меньше надоело это делать, потому что это почти никогда не стоит усилий.

Мне не нравится передавать код ошибки вызывающей функции для OOM, потому что это эквивалентно сообщению вызывающей стороне: "Я потерпел неудачу, и вы ничего не можете с этим поделать". Вместо этого я предпочитаю быстро падать, поэтому получаемый дамп будет максимально поучительным.

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

m->data = realloc(m->data, m->max * sizeof(void*)); 

плохо. Если realloc дает сбой, возвращает нулевой указатель, но не освобождает старую память. Код выше обнулит ваш m->data в то время как старый блок памяти, ранее указанный m->data скорее всего, станет утечка памяти (если у вас нет других ссылок на него).

Возвращаемое значение realloc сначала должен храниться в отдельном указателе

void **new_data;
...
new_data = realloc(m->data, m->max * sizeof(void*)); 

Затем вы можете проверить успех / неудачу и изменить значение m->data в случае успеха

if (new_data != NULL)
  m->data = new_data;
else
  /* whatever */;

Это полностью ваша проблема! Вот несколько критериев:

  • Вы просили эту память по причине. Если он недоступен, работа вашей программы обречена или она может продолжаться? Если первое, вы хотите прекратить свою программу с сообщением об ошибке; в противном случае вы можете как-то отобразить сообщение об ошибке и продолжить.

  • Есть ли возможность обменять время на пространство? Не могли бы вы ответить на любую операцию, которую вы пытались использовать, используя алгоритм, который использует меньше памяти? Это звучит как большая работа, но в действительности это будет возможность для продолжения работы вашей программы, несмотря на нехватку памяти на начальном этапе.

  • Было бы неправильно, чтобы ваша программа продолжала хромать без этих данных и нехватки памяти? Если это так, вы должны завершить с сообщением об ошибке. Гораздо лучше убить вашу программу, чем слепо продолжать обработку неверных данных.

  1. Узнайте, как инфраструктура приложения обрабатывает OOM. Многие просто не будут обращаться с ООМ. Большую часть времени фреймворк не будет работать должным образом в условиях отсутствия свободной оперативной памяти, если он не очень четко и недвусмысленно скажет, что он будет работать. Если фреймворк не будет обрабатывать OOM и является многопоточным (в настоящее время многие из них), во многих случаях OOM станет концом шоу для процесса. Даже если он не многопоточный, он все еще может быть близок к краху. Независимо от того, выходите ли вы из процесса или из структуры, может быть спорным вопросом; предсказуемый немедленный выход может быть немного лучше, чем сбой в некоторой полуслучайной точке в ближайшем будущем.

  2. Если вы используете отдельный пул вспомогательной памяти специального назначения (т. Е. Не ваш обычный malloc) для четко определенного набора операций, которые ограничены только использованием памяти OOM (т. Е. Текущая операция откатывается или прерывается чисто OOM для пула дополнительной памяти, а не для всего процесса или пула основной памяти), и этот субпул также не используется платформой приложения, или если ваша структура и ВЕСЬ остальной части приложения предназначены для поддержания значимого состояние и продолжение работы в условиях отсутствия свободной оперативной памяти (редко, но не случайно в режиме ядра и некоторых типах системного программирования) вы можете быть правы, возвращая код ошибки, а не сбивая процесс.

  3. В идеале большая часть выделений памяти (или даже, в идеале, все выделения) для части обработки должна быть выделена как можно скорее при обработке, в идеале, до ее надлежащего начала, чтобы минимизировать проблемы потери целостности данных и / или количества требуется кодирование отката, если оно терпит неудачу. На практике много времени, чтобы сэкономить время и затраты на программирование проектов, сохранить приложения целостности данных, полагаться на транзакции базы данных и требовать, чтобы пользователь / специалист по поддержке обнаружил сбой графического интерфейса пользователя (или сервера) и перезапустил приложение, когда вышел из строя. ошибки памяти происходят вместо того, чтобы быть записанными для того, чтобы справляться с любым отобранным из тысячи возможных ситуаций OOM и выполнять откат для них наилучшим образом. Затем усилия сосредоточены на попытках ограничить подверженность приложения перегрузочным ситуациям, что может включать дополнительную проверку и ограничения на размер данных и одновременные соединения и запросы.

  4. Даже если вы проверяете, сколько памяти сообщается как доступное, часто другой код может выделять или освобождать память, как вы, изменяя основу для проверки памяти и, возможно, приводя к OOM. Поэтому проверка доступной свободной оперативной памяти перед ее выделением часто не является надежным решением проблемы обеспечения того, чтобы ваше приложение работало в пределах доступных ограничений оперативной памяти и поддерживало целостность данных достаточно времени, чтобы удовлетворить пользователей.

  5. Лучшая ситуация - узнать, сколько памяти требуется вашему приложению во всех возможных случаях, включая любые накладные расходы инфраструктуры, и сохранить эту цифру в объеме оперативной памяти, доступной вашему приложению, но системы часто настолько сложны, что диктуют внешние зависимости. размер данных, поэтому достижение этого может быть нереальным.

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

Что касается realloc:

Проверьте возвращаемое значение из realloc - поместите его во временную переменную. Будьте внимательны, если он равен NULL, если запрошенный новый размер был>0. В других случаях поместите его в вашу временную переменную:

например

    void* temp = realloc(m->data, m->max * sizeof(void*));
    if (m->max!=0&&temp==NULL) { /* crash or return error */ }
    m->data =(void**)temp;

РЕДАКТИРОВАТЬ

Изменено "большинство случаев" на "много случаев" в (1).

Я признаю, что вы сказали, что "что-то можно сделать", если память не может быть выделена. Но управление памятью - это глобальное соображение (!).

Я столкнулся с проблемой. Конфигурация: ОС:win7(64);IDE:vs2013; Отладка (Win32).
Когда мой realloc возвратил нуль из-за памяти в. У меня есть два решения:

1. Измените свойство проекта, чтобы включить БОЛЬШИЕ АДРЕСА.
2. Измените мою платформу решения с Win32 на x64.

Есть также другая тонкая ошибка, которая может исходить от realloc. Утечка памяти, вызванная возвращаемым указателем NULL, довольно хорошо известна (но встречается довольно редко). В моей программе время от времени происходил сбой, вызванный вызовом realloc. У меня была динамическая структура, которая автоматически настраивала свой размер с помощью realloc, похожего на этот:

m->data = realloc(m->data, m->max * sizeof(void*)); 

Ошибка, которую я сделал, состояла в том, чтобы не проверять m->max == 0, что освободило область памяти. И сделал из моего m->data указатель устаревший.

Я знаю, что это немного не по теме, но это была единственная реальная проблема, которую я когда-либо имел с realloc.

Другие вопросы по тегам