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

Вопрос говорит все это, но здесь является примером:

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;
}

Как я могу обработать исчерпывание памяти и не ПУСТОЙ все мои данные?

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

19
задан Nick Van Brunt 31 December 2009 в 18:38
поделиться

7 ответов

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

tmp = realloc(orig, newsize);
if (tmp == NULL)
{
    // could not realloc, but orig still valid
}
else
{
    orig = tmp;
}
24
ответ дан 30 November 2019 в 02:44
поделиться

Это тема с горячей клавишей, так как по существу существует 2 школы мысли по этому предмету

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

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

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

7
ответ дан 30 November 2019 в 02:44
поделиться

Первое правило, которому вы должны следовать при работе с 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->данные в случае успеха

if (new_data != NULL)
  m->data = new_data;
else
  /* whatever */;
3
ответ дан 30 November 2019 в 02:44
поделиться

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

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

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

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

2
ответ дан 30 November 2019 в 02:44
поделиться

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

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

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

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

.
1
ответ дан 30 November 2019 в 02:44
поделиться
  1. Узнайте, как фреймворк приложений обрабатывает OOM. Многие просто не будут работать с OOM. В большинстве случаев фреймворк не будет корректно работать в условиях отсутствия свободной памяти, если только он не скажет очень ясно и недвусмысленно где-нибудь, что он будет работать. Если фреймворк не будет обрабатывать OOM и будет многопоточным (многие из них в настоящее время), то OOM во многих случаях будет концом шоу для этого процесса. Даже если он не является многопоточным, он все равно может быть близок к краху. Выход из процесса или из фреймворка может быть спорной точкой; предсказуемый немедленный выход может быть просто немного лучше, чем крах в какой-нибудь полуслучайной точке в ближайшем будущем.

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

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

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

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

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

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

Проверьте возвращаемое из realloc значение - поместите его во временную переменную. В других случаях поместите его во временную переменную:

например

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

EDIT

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

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

.
2
ответ дан 30 November 2019 в 02:44
поделиться

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

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

Never do:

a = realloc(a, size);

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

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

Second point I want to make is minor and may not be so critical, but it's good to know about it anyway: увеличение выделяемой памяти в коэффициент f - это хорошо. Допустим, сначала вы malloc() n байт. Затем нужно больше памяти, и Вы realloc() с размером n×f. Затем вам нужно больше памяти, поэтому вам нужно n×f2 байт. Если Вы хотите, чтобы realloc() использовали пространство из двух предыдущих блоков памяти, необходимо убедиться, что n×f2 ≤ n + n×f. Решая это уравнение, получаем f≤ (sqrt(5)+1)/2 = 1,618 (коэффициент Golden ratio). В большинстве случаев я использую коэффициент 1.5.

8
ответ дан 30 November 2019 в 02:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: