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

StackOverflow https://stackoverflow.com/questions/1986538

  •  22-09-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

Стандартный метод — ввести новую переменную, в которой будет храниться результат операции 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() н сначала байты.Тогда вам нужно больше памяти, поэтому вы realloc() с размером n×f.Тогда вам нужно больше памяти, поэтому вам нужно n×f2 байты.Если ты хочешь realloc() чтобы использовать пространство из двух предыдущих блоков памяти, вы должны убедиться, что n×f2 ≤ n + n×f.Решая это уравнение, получаем f≤ (sqrt(5)+1)/2 = 1,618 ( Золотое сечение).Я использую коэффициент 1.5 в большинстве случаев.

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

  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 — поместите его во временную переменную.Обратите внимание, что значение NULL равно 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) слово «большинство случаев» заменено на «много случаев».

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top