#c #linux #exception #g #heap-memory
#c #linux #исключение #g #куча-память
Вопрос:
Я работаю над довольно большим приложением для SIP-телефонии, и иногда, когда мы используем интегрированный веб-интерфейс (написанный с использованием tntnet) при большой загрузке вызовов, программа завершает работу из-за выброса std::bad_alloc. Используются сотни потоков (по 3 на активный вызов), поэтому расположение кода, вызывающего исключение, довольно случайное, но всегда после использования GUI.
Теперь я понимаю, что std::bad_alloc может быть выброшен при нехватке памяти, чего в данной ситуации нет. Я также думаю, что это может быть выброшено при повреждении кучи, которое я все еще ищу, где бы оно ни находилось в базе кода.
Но мой вопрос в том, есть ли какие-либо другие причины, по которым std::bad_alloc будет выброшен, кроме нехватки памяти или повреждения кучи? Я использую GNU g в Linux.
Комментарии:
1. Насколько я знаю или видел, нет.
2. bad_alloc выбрасывается только при сбое выделения памяти, хотя, как вы заметили, если ваша программа делает что-либо неопределенное, она может делать что угодно, включая выбрасывание bad_alloc в любое время после неопределенного действия
3. @Крис Додд: или до неопределенного поведения, я полагаю!
Ответ №1:
Скорее всего, у вас действительно закончилась память. Это была бы чрезвычайно редкая ошибка повреждения кучи, которая последовательно приводила к выбросу только bad_alloc. Это было бы похоже на каракули с хирургической точностью.
Возможно, в коде просто ошибка, из-за которой выделяется огромный объем памяти. Но можно было бы ожидать, что исключение будет выдаваться, по крайней мере, большую часть времени, в этом самом коде. Тот факт, что исключение поступает из нескольких разных мест, говорит против этого.
Сильная фрагментация может вызвать проблему, особенно для платформ с плохими реализациями malloc
. Это редко, но случается.
Одна вещь, которую я бы сделал немедленно — перехватил исключение и вызвал функцию, которая сохраняет копию /proc/self/maps
. Это даст вам хорошее представление о максимальном использовании памяти процессом. Вы можете судить, соответствует ли это каким-либо ограничениям платформы, политики или аппаратного обеспечения.
Ответ №2:
В Linux текущее ограничение адресного пространства может использоваться для искусственного ограничения объема памяти, который может использовать процесс. Вы можете вручную установить это с помощью setrlimit(RLIMIT_AS, ...)
. Это также может быть установлено для всей оболочки при bashrc
использовании ulimit -v
. Это также может быть установлено для всей системы в /etc/security/limits.conf
. Возможно, где-то даже есть запись /proc / sys для этого, я не уверен.
Если достигнут предел адресного пространства, ваш процесс выдаст std::bad_alloc при попытке выделить больше памяти. В 64-разрядной системе это может быть хорошей «защитой», позволяющей убедиться, что плохое приложение или библиотека не исчезнут с доступной памятью и не заставят систему перейти на swap или вообще прекратить работу. Убедитесь, что программа сама не установила это где-нибудь, и убедитесь, что остальная часть среды также не установила это. Вы можете просто вставить некоторый код в середине программы в какое-нибудь место для вызова getrlimit(RLIMIT_AS, ...)
, чтобы быть уверенным, что он где-нибудь не застрял.
Возможно, более распространенной причиной (кроме фактического исчерпания памяти, конечно) является перенос целого числа без знака вокруг случая, когда uin32_t или uint64_t используются для выделения памяти, но равняются 0 и из них вычитается 1, что приводит к очень большому выделению запроса (в 64-битах это составило бы много тысяч петабайт).
В любом случае, лучшие способы отследить это — с помощью GDB. Если ваше приложение вообще не использует исключения (и, следовательно, вообще не имеет инструкций catch), вы можете включить core files ( ulimit -c unlimited
). При следующем сбое программы ОС сгенерирует основной файл и, загрузив его в GDB, немедленно предоставит вам обратную трассировку, показывающую, где произошел сбой программы.
Если у вас есть несколько (но не так много) мест, где используется функция try и она улавливает эти неправильные выделения, а не просто комментирует их во время отладки этой проблемы, вы можете запустить приложение в GDB и использовать catch throw
команду для прерывания GDB при каждом возникновении исключения. Чтобы любой из этих способов работал, никогда не компилируйте с -fomit-frame-pointer
и всегда компилируйте (даже при использовании -O3
) с -ggdb
.