c++ - утечку - си утечка памяти




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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

Снятие стека - ваш друг;)

EDIT: только осознал, что вопрос также был помечен 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, поэтому я не понимаю, почему они заслуживают обсуждения. Но если вы находитесь на такой платформе, это нужно иметь в виду.


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

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

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

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

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


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

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

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





linux