Подтвердить что ты не робот

Какой изящный способ обработки ситуаций с памятью в C/С++?

Я пишу приложение для кеширования, которое потребляет большие объемы памяти.

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

Если вызов для выделения даже простого объекта завершается с ошибкой, возможно, что даже вызов syslog также потерпит неудачу?

EDIT: Хорошо, возможно, я должен уточнить этот вопрос. Если malloc или new возвращает значение NULL или 0L, это по существу означает, что вызов был неудачным, и по какой-то причине он не может дать вам память. Итак, что было бы разумным в этом случае?

EDIT2: Я только понял, что вызов "нового" может вызвать исключение. Это можно поймать на более высоком уровне, поэтому я могу, возможно, изящно выйти дальше. В этот момент его можно восстановить даже в зависимости от того, сколько памяти освобождено. По крайней мере, я должен с этой точки зрения, возможно, что-нибудь зарегистрировать. Поэтому, пока я видел код, который проверяет значение указателя после нового, это не нужно. Находясь в C, вы должны проверить возвращаемое значение для malloc.

4b9b3361

Ответ 1

Хорошо, если вы находитесь в случае сбоя в распределении памяти, вы получите исключение std::bad_alloc. Исключение вызывает раскрутку стека вашей программы. По всей вероятности, внутренние петли вашей прикладной логики не будут обрабатывать из памяти условия, только более высокие уровни вашего приложения должны делать это. Поскольку стек разматывается, значительная часть памяти будет свободна - на самом деле это будет почти вся память, используемая вашей программой.

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

Разблокировка стека - ваш друг;)

РЕДАКТИРОВАТЬ: Просто понял, что вопрос также был помечен C - если это так, то вы должны иметь свои функции, освобождая их внутренние структуры вручную, когда не найдены условия памяти; не для этого происходит утечка памяти.

EDIT2: Пример:

#include <iostream>
#include <vector>

void DoStuff()
{
    std::vector<int> data;
    //insert a whole crapload of stuff into data here.
    //Assume std::vector::push_back does the actual throwing
    //i.e. data.resize(SOME_LARGE_VALUE_HERE);
}

int main()
{
    try
    {
        DoStuff();
        return 0;
    }
    catch (const std::bad_alloc& ex)
    {   //Observe that the local variable `data` no longer exists here.
        std::cerr << "Oops. Looks like you need to use a 64 bit system (or "
                     "get a bigger hard disk) for that calculation!";
        return -1;
    }
}

EDIT3: Хорошо, по словам комментаторов, есть системы, которые не соответствуют стандарту в этом отношении. С другой стороны, на таких системах вы в любом случае будете SOL, поэтому я не понимаю, почему они заслуживают обсуждения. Но если вы на такой платформе, это то, о чем нужно помнить.

Ответ 2

Разве этот вопрос не делает предположений относительно избыточной памяти?

I.e. ситуация с памятью не может быть восстановлена! Даже если у вас нет памяти, вызовы malloc и других распределителей могут по-прежнему выполняться до тех пор, пока программа не попытается использовать память. Затем, BAM!, некоторый процесс убивается ядром, чтобы удовлетворить нагрузку на память.

Ответ 3

У меня нет особого опыта работы в Linux, но я потратил много времени на работу в видеоигры на игровых консолях, где исчерпывающая память верботена и инструменты Windows.

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

Запишите/украдите свой собственный менеджер памяти и направьте его на выделение из этих буферов. Затем используйте его, последовательно, в своем приложении или воспользуйтесь опцией gcc --wrap для переадресации вызовов из malloc и друзей соответствующим образом. Если вы используете какие-либо библиотеки, которые не могут быть направлены на вызов в диспетчер памяти, отпустите их, потому что они просто мешат вам. Отсутствие чрезмерных вызовов управления памятью свидетельствует о более глубоких проблемах; вам лучше без этого конкретного компонента. (Примечание: даже если вы используете --wrap, поверьте мне, это все еще свидетельствует о проблеме! Жизнь слишком коротка, чтобы использовать те библиотеки, которые не позволяют вам перегружать управление памятью!)

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

Этот подход может звучать как боль в заднице. Ну... это так. Но это просто, и для этого стоит приложить немного усилий. Я думаю, что есть цитата Кернигана и/или Ритче об этом.

Ответ 4

Если ваше приложение, скорее всего, выделит большие блоки памяти и риски, попадающие в пределы каждого процесса или виртуальной машины, до тех пор, пока фактическое прохождение распределения не будет сложной ситуацией, из которой можно восстановить. Ко времени malloc возвращает NULL или new throws std::bad_alloc, все может быть слишком далеко, чтобы надежно восстановить. В зависимости от стратегии восстановления, многие операции могут по-прежнему требовать распределения кучи, поэтому вам нужно быть предельно осторожными, какие подпрограммы вы можете положиться.

Еще одна стратегия, которую вы, возможно, пожелаете рассмотреть, - это запросить ОС и контролировать доступную память, активно управляя вашими распределениями. Таким образом, вы можете избежать выделения большого блока, если знаете, что он, скорее всего, потерпит неудачу и, следовательно, получит больше шансов на восстановление.

Кроме того, в зависимости от шаблонов использования памяти использование настраиваемого распределителя может дать вам лучшие результаты, чем стандартный встроенный malloc. Например, некоторые шаблоны распределения могут фактически приводить к фрагментации памяти с течением времени, поэтому, даже если у вас есть свободная память, доступные блоки в области кучи могут не иметь доступного блока нужного размера. Хорошим примером этого является Firefox, который переключился на dmalloc и увидел значительное увеличение эффективности памяти.

Ответ 5

Я не думаю, что захват неудачи malloc или new поможет вам в вашей ситуации. linux выделяет большие куски виртуальных страниц в malloc с помощью mmap. При этом вы можете оказаться в ситуации, когда вы выделяете гораздо больше виртуальной памяти, чем у вас (real + swap).

Затем программа будет работать с ошибкой намного позже с помощью segfault (SIGSEGV) при записи на первую страницу, для которой нет места в swap. Теоретически вы можете проверить такие ситуации, написав обработчик сигналов, а затем загрязняя все страницы, которые вы выделяете.

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

Ответ 6

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

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

Лично я убежден в дизайне Varnish: операционная система предлагает услуги для решения многих проблем, и имеет смысл использовать эти службы (незначительное редактирование):

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

Squid создает объект HTTP в ОЗУ и быстро используется после создания. Затем через некоторое время он не получает больше хитов, и ядро ​​это замечает. Затем кто-то пытается получить память от ядра для чего-то, и ядро ​​решает вытолкнуть эти неиспользуемые страницы памяти для замены места и использовать (кеш-RAM) более разумно для некоторых данных, которые фактически используются программой. Это, однако, делается без знания Squid. Squid по-прежнему считает, что эти объекты http находятся в ОЗУ, и они будут, в то время как они будут пытаться получить к ним доступ, но до тех пор RAM используется для чего-то продуктивного....

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

Здесь мы переключаемся на высокоскоростную камеру: Squid calls write (2), адрес, который он дает, является "виртуальным адресом", а ядро ​​имеет его как "не у себя дома"....

Ядро пытается найти свободную страницу, если ее нет, она займет немного используемую страницу где-то, вероятно, еще один маленький используемый объект Squid, напишет его на пейджинг... на диске ( "swap" область "), когда эта запись завершается, она будет считывать из другого места в пейджинговом пуле данные, которые он" выгружал "на теперь неиспользуемую страницу ОЗУ, фиксировать таблицы подкачки и повторять неудачную инструкцию....

Итак, теперь Squid имеет объект на странице в ОЗУ и записывает на диск два места: одну копию в пространстве подкачки операционной системы и одну копию в файловой системе....

Вот как это делает лак:

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

Если/когда ядро ​​решает, ему нужно использовать ОЗУ для чего-то другого, страница будет записана в файл резервной копии, а страница RAM снова будет использована в другом месте.

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

И что это. Лак на самом деле не пытается контролировать то, что кэшируется в ОЗУ, а что нет, ядро ​​имеет поддержку кода и аппаратного обеспечения, чтобы хорошо справиться с этим, и это делает хорошую работу.

Вам вообще не нужно писать код кэширования.

Ответ 7

Как уже говорилось, исчерпывающая память означает, что все ставки отключены. ИМХО лучший способ справиться с этой ситуацией - потерпеть неудачу изящно (в отличие от простого сбоя!). Кэш может выделять разумный объем памяти при создании экземпляра. Размер этой памяти будет равен сумме, которая при ее освобождении позволит программе законно прекратиться. Когда ваш кеш обнаруживает, что память становится низкой, тогда она должна освободить эту память и спровоцировать изящное завершение работы.

Ответ 8

Я пишу приложение для кеширования, которое потребляет большие объемы памяти. Надеюсь, я достаточно хорошо справляюсь с моей памятью, но я просто думаю о том, что делать, если у меня заканчивается память.

Если вы пишете deamon, который должен работать 24/7/365, тогда вам не следует использовать динамическое управление памятью: заранее предопределите всю память и управляйте ею, используя slab allocator/механизм пула памяти. Это также снова защитит фрагментацию кучи.

Если вызов для выделения даже простого объекта завершается с ошибкой, возможно ли, что даже вызов syslog также завершится неудачей?

Не следует. Это частично причина, по которой syslog существует как syscall: это приложение может сообщать об ошибке независимо от ее внутреннего состояния.

Если malloc или new возвращает значение NULL или 0L, это по существу означает, что вызов был неудачным, и по какой-то причине он не может дать вам память. Итак, что было бы разумным в этом случае?

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

Для обычной памяти кучи malloc() возврат 0 обычно означает:

  • что вы исчерпали кучу, и если ваше приложение не освободит память, далее malloc() не получится.

  • неправильный размер размещения: довольно распространенная ошибка кодирования для смешивания подписных и неподписанных типов при расчете размера блока. Если размер заканчивается ошибочно отрицательным, передается malloc(), где size_t ожидается, он становится очень большим.

Таким образом, в некотором смысле это также не является неправильным для abort() для создания основного файла, который может быть проанализирован позже, чтобы узнать, почему возвращается malloc() 0. Хотя я предпочитаю (1) включать в сообщение об ошибке попытку размещения и (2) попытаться продолжить. Если приложение выйдет из строя из-за другой проблемы с памятью по дороге (*), в любом случае это приведет к созданию файла ядра.

(*) Из моего опыта создания программного обеспечения с динамической памятью, устойчивой к ошибкам malloc(), я вижу, что часто malloc() не надежно возвращает 0. После первых попыток, возвращающих 0, следует успешный malloc() возвращающий действительный указатель. Но первый доступ к указанной памяти приведет к сбою приложения. Это мой опыт работы как с Linux, так и с HP-UX, и я видел аналогичную модель на Solaris 10. Поведение не уникально для Linux. Насколько мне известно, единственный способ сделать приложение на 100% устойчивым к проблемам с памятью - заранее предустановить всю память. И это обязательно для критически важных задач безопасности, жизнеобеспечения и приложений операторского класса - им не разрешено управлять динамической памятью на этапе инициализации.

Ответ 9

Я не знаю, почему многие разумные ответы проголосовали. В большинстве серверных сред нехватка памяти означает, что у вас есть утечка где-то, и что нет смысла "освобождать память и пытаться продолжить". Характер С++ и особенно стандартная библиотека заключается в том, что он требует распределения все время. Если вам повезет, вы можете освободить некоторую память и выполнить чистое выключение или, по крайней мере, выпустить предупреждение.

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

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

Для большинства из нас, вероятно, мы должны разработать наше программное обеспечение, чтобы оно оставило вещи в полезном состоянии, когда оно освобождается из-за ошибки из памяти.

Если вы не какой-то мозговой хирург, делать еще нечего.

Кроме того, очень часто вы даже не получаете std:: bad_alloc или что-то в этом роде, вы просто получите указатель в своем malloc/new и только умрете, когда вы на самом деле попытаетесь коснуться всего этого Память. Этого можно предотвратить, отключив overcommit в операционной системе, но все же.

Не рассчитывайте на возможность общаться с SIGSEGV, когда вы прикасаетесь к памяти, что ядро ​​надеялось, что вы этого не сделаете. Я не совсем уверен, как это работает на окнах, но я уверен, они тоже перекомпонуйте.

В целом, это не одно из сильных пятен С++.