Утечки памяти в программах на С/С++ — история нескольких багов

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

Кодопотерятор памяти

Очередное тестирование в QA выявило небольшую постоянную утечку памяти под нагрузкой в новой версии продукта. Воспроизведение не представляло проблемы, но первоначальный прогон программы под Valgrind не выявил утечек.

После некоторых раздумий была включена опция --leak-check=full и Valgrind начал отчитываться об утечках. Но среди ожидаемых утечек разного рода статических переменных, зачастую осознанно не освобождаемых, не находилось ничего, напоминающего утечку. Обычно при прогоне нескольких тысяч итераций несложно выделить аналогичное количество потерянных блоков памяти выделенных malloc-ом. В данном-же случае утечка на 10,000 запросов к серверу была минимальна.

После анализа стеков выделений памяти и отсева ожидаемых случаев, остался только один кандидат, который отвечал за несколько десятков потерянных блоков — количество, абсолютно не ассоциирующееся с 10,000 запросами. Но этому нашлось объяснение — выделения памяти происходили в классе строки STL, который активно использует memory pool для уменьшения количества выделений памяти. Поэтому вместо 10,000 потерянных блоков памяти Valgrind отчитывался о 40+. Стек вызовов выглядел примерно так:

==15882== 76,400 bytes in 8 blocks are definitely lost in loss record 2 of 3
==15882==      at 0x401B007: operator new(unsigned int) (vg_replace_malloc.c:214)
==15882==      by 0x40A40F0: std::__default_alloc_template<true, 0>::_S_chunk_alloc(unsigned int, int&) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x40A3FFC: std::__default_alloc_template<true, 0>::_S_refill(unsigned int) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x40A3B6B: std::__default_alloc_template<true, 0>::allocate(unsigned int) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x40A9B67: std::string::_Rep::_S_create(unsigned int, std::allocator<char> const&) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x40A9C98: std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned int) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x40A7A05: std::string::reserve(unsigned int) (in /usr/lib/libstdc++.so.5.0.3)
==15882==      by 0x8049826: std::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char,
std::char_traits<char>, std::allocator<char> >(char const*, std::basic_string<char, std::char_traits<char>,
std::allocator<char> > const&) (basic_string.tcc:619)
==15882==      by 0x804956A: A::A(A const&) (class_a.cpp:20)
==15882==      by 0x80491BC: foo(int) (test.cpp:23)
==15882==      by 0x80492EA: main (test.cpp:32)


Вроде-бы источник утечки памяти был найден, но код выглядел абсолютно невинно — вызов функции с передачей ей временной копии объекта:

doSomething( condition ? Object( params ) : getObject() );

Мы были уже точно уверены, что память теряется в этой строке, и начали смотреть на код, генерируемый компилятором для данной строки. Вроде-бы все было на месте — вызов “basic_string::length()”, вызов конструктора класса параметра для одной ветви условия, вызов “Parent::getB()” и конструктора копирования для другой ветви, вызов собственно функции “A::create”, освобождения временных объектов — всё, кроме вызова деструктора временной копии класса — созданной на стеке, но содержащей внутри копию строки, которая и не освобождалась в результате!

110:   return A::create(b1, b2, s.length() > 0 ? B(s) : getB());
  13d4:     83 ec 0c                   sub    $0xc,%esp
  13d7:     8d 45 d8                   lea    0xffffffd8(%ebp),%eax
  13da:     50                         push   %eax
  13db:     e8 fc ff ff ff             call   std::basic_string<wchar_t>::length() const <<=========
  13e0:     83 c4 10                   add    $0x10,%esp
  13e3:     85 c0                      test   %eax,%eax
  13e5:     74 18                      je     13ff <A::create+0x341>
  13e7:     83 ec 08                   sub    $0x8,%esp
  13ea:     8d 45 d8                   lea    0xffffffd8(%ebp),%eax
  13ed:     50                         push   %eax
  13ee:     8d 85 f8 fe ff ff          lea    0xfffffef8(%ebp),%eax
  13f4:     50                         push   %eax
  13f5:     e8 fc ff ff ff             call   B::B( std::basic_string<wchar_t> const & ) <<=========
  13fa:     83 c4 10                   add    $0x10,%esp
  13fd:     eb 21                      jmp    1420 <A::create+0x362>
  13ff:     83 ec 08                   sub    $0x8,%esp
  1402:     83 ec 04                   sub    $0x4,%esp
  1405:     ff 75 0c                   pushl  0xc(%ebp)
  1408:     e8 fc ff ff ff             call   Parent::getB() <<=========
  140d:     83 c4 08                   add    $0x8,%esp
  1410:     50                         push   %eax
  1411:     8d 85 f8 fe ff ff          lea    0xfffffef8(%ebp),%eax
  1417:     50                         push   %eax
  1418:     e8 fc ff ff ff             call   B::B( B const & ) <<=========
  141d:     83 c4 10                   add    $0x10,%esp
  1420:     83 ec 0c                   sub    $0xc,%esp
  1423:     8d 85 f8 fe ff ff          lea    0xfffffef8(%ebp),%eax
  1429:     50                         push   %eax
  142a:     0f b6 45 f6                movzbl 0xfffffff6(%ebp),%eax
  142e:     50                         push   %eax
  142f:     0f b6 45 f7                movzbl 0xfffffff7(%ebp),%eax
  1433:     50                         push   %eax
  1434:     ff 75 0c                   pushl  0xc(%ebp)
  1437:     ff 75 08                   pushl  0x8(%ebp)
  143a:     e8 fc ff ff ff             call   A::create(bool, bool, B) <<=========
  143f:     83 c4 1c                   add    $0x1c,%esp
  1442:     83 ec 0c                   sub    $0xc,%esp
  1445:     8d 85 68 ff ff ff          lea    0xffffff68(%ebp),%eax
  144b:     50                         push   %eax
  144c:     e8 fc ff ff ff             call   BS<100, char>::~BS()
  1451:     83 c4 10                   add    $0x10,%esp
  1454:     83 ec 0c                   sub    $0xc,%esp
  1457:     8d 45 d8                   lea    0xffffffd8(%ebp),%eax
  145a:     50                         push   %eax
  145b:     e8 fc ff ff ff             call   std::basic_string<wchar_t>::~basic_string()
  1460:     83 c4 10                   add    $0x10,%esp
  1463:     eb 55                      jmp    14ba <A::create+0x3fc>
  1465:     89 85 f0 fe ff ff          mov    %eax,0xfffffef0(%ebp)
  146b:     8b b5 f0 fe ff ff          mov    0xfffffef0(%ebp),%esi
  1471:     83 ec 0c                   sub    $0xc,%esp
  1474:     8d 85 68 ff ff ff          lea    0xffffff68(%ebp),%eax
  147a:     50                         push   %eax
  147b:     e8 fc ff ff ff             call   BS<100, char>::~BS()
  1480:     83 c4 10                   add    $0x10,%esp
  1483:     89 b5 f0 fe ff ff          mov    %esi,0xfffffef0(%ebp)
  1489:     eb 06                      jmp    1491 <A::create+0x3d3>
  148b:     89 85 f0 fe ff ff          mov    %eax,0xfffffef0(%ebp)
  1491:     8b b5 f0 fe ff ff          mov    0xfffffef0(%ebp),%esi
  1497:     83 ec 0c                   sub    $0xc,%esp
  149a:     8d 45 d8                   lea    0xffffffd8(%ebp),%eax
  149d:     50                         push   %eax
  149e:     e8 fc ff ff ff             call   std::basic_string<wchar_t>::~basic_string()
  14a3:     83 c4 10                   add    $0x10,%esp
  14a6:     89 b5 f0 fe ff ff          mov    %esi,0xfffffef0(%ebp)
  14ac:     83 ec 0c                   sub    $0xc,%esp
  14af:     ff b5 f0 fe ff ff          pushl  0xfffffef0(%ebp)
  14b5:     e8 fc ff ff ff             call   _Unwind_Resume


Так и не найдя вызов деструктора к коде, начали искать похожие проблемы, и практически сразу нашли "gcc 3.2 bug 9946 — object destructor not called, potentially causing a memory leak".

Проблема была в генерации кода для оператора “?:”, и решалась либо обновлением компилятора, либо косметическим изменением оператора “?:” на простой if().

// Есть функция, возвращающая константную ссылку на объект
const Object & getObject();

// Другая функция принимает объект по значению
void doSomething( Object obj );

// При вызове подобном нижеследующему, может произойти утечка памяти.
// Автоматически созданный на стеке экземпляр объекта может быть не освобожден,
// приводя к утечке памяти если экземпляр содержал динамически выделенную память.
doSomething( condition ? Object( params ) : getObject() );


Простая тестовая программа выводила следующее (обратите внимание на количество созданных и освобожденных экземпляров класса А):

main() start
A::A( 'on stack' )
B::B()
A::A( 'static instance' )
A::A( 'copy of static instance' )
B::boo()
B::~B()
A::~A( 'on stack' )
main() end
A::~A( 'static instance' )
Class A created 3 times and destroyed 2 times
Class B created 1 times and destroyed 1 times


Проблема происходила только при генерации 32-битного кода в gcc 3.2.3, и не происходила в 64 битном коде, или коде сгенерированном более поздними версиями компилятора.

Я — не я, и память не моя

Одно время я поддерживал и дорабатывал программу для сбора данных и передачи их на сервер. Эта программа работала на добром десятке платформ, в том числе на Linux. Так как программа была коммерческая, то компилировалась она компилятором, соответствующим минимальной поддерживаемой версии Linux, в нашем случае gcc 3.3.x, и предоставлялись исполняемые файлы.

В один прекрасный момент, в нашем отделе QA зарегистрировали и даже сумели воспроизвести (во время длительного, несколько дней, теста под большой нагрузкой) падение программы по причине нехватки памяти — процесс съедал 3GB памяти и благополучно падал, создавая core dump аналогичного объема. Причем взрывной рост использования памяти происходил буквально за 10-15 минут до печального конца, и в это время нагрузка процессора была порядка 12% (на сервере было 4 двух-ядерных процессора, так что один поток, крутящийся в цикле, как раз и должен использовать 12.5%).

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

(gdb) where
#0  0xffffe410 in __kernel_vsyscall ()
#1  0xb7dbd8d0 in raise () from /lib/libc.so.6
#2  0xb7dbeff3 in abort () from /lib/libc.so.6
#3  0xb7f86da5 in dlopen () from /usr/lib/libstdc++.so.5
#4  0xb7f86de2 in std::terminate () from /usr/lib/libstdc++.so.5
#5  0xb7f85e89 in __cxa_allocate_exception () from /usr/lib/libstdc++.so.5
#6  0xb78f7f07 in Uuid::Uuid () from .../lib32/libourlibrary.so
#7  0xb782409d in ...


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

Поиск похожих стеков вызовов не дал практически никаких результатов, и ситуация была тупиковая. Надо было как-то обнаружить причину утечки памяти без использования отладчика.

В одной реализации библиотеки выделения памяти для С++, с которой я сталкивался, каждый блок, выделяемый под новый экземпляр класса, в отладочной версии библиотеки помечался строкой, содержащей имя этого класса. Такой способ позволял легко определит по core dump-y, какие объекты того или иного типа были выделены. Я попробовал поискать строки, содержащиеся в core dump файле, запустив сначала программу «strings», а затем отсортировав с «sort».

Любопытно, но обнаружилось, что файл содержит 32,123,751 строк вида «++CCUNG0o» — только эти строки занимали около 275 MB в файле размером 3 GB. При поиске этих строк в файле выяснилось, что каждая такая сигнатура начинала блок размером 96 байт (96b * 32,000,000 = 3Gb!!!)

Блоки начинались инвертированной строкой «++CCUNG0o» с нулем в начале (так как она инвертированная), и отличались только парой из четырех байт в разных местах, образуя, по-видимому, связанный список.

0x248b818: 0x00 0x2b 0x2b 0x43 0x43 0x55 0x4e 0x47 <<== строка «++CCUNG0o»
0x248b820: 0x30 0x6f 0xf8 0xb7 0x00 0x00 0x00 0x00
0x248b828: 0x6c 0xa8 0x78 0xb7 0x00 0x00 0x00 0x00
0x248b830: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x248b838: 0xa8 0x0e 0xd3 0xb7 0x3c 0xa2 0xfa 0xb7
0x248b840: 0xe8 0xae 0x82 0xb7 0x65 0x00 0x00 0x00
0x248b848: 0xc0 0x0e 0xd3 0xb7 0x38 0x9c 0xc8 0xb7
0x248b850: 0xf4 0x9b 0x04 0x08 0xe4 0x9c 0x04 0x08
0x248b858: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x248b860: 0x03 0x00 0x00 0x00 0xbe 0x80 0x79 0xb7
0x248b868: 0x58 0x80 0x79 0xb7 0x54 0xa9 0x78 0xb7
0x248b870: 0x98 0xb8 0x48 0x02 0x00 0x00 0x00 0x00

0x248b878: 0x00 0x2b 0x2b 0x43 0x43 0x55 0x4e 0x47 <<== строка «++CCUNG0o»
0x248b880: 0x30 0x6f 0xf8 0xb7 0x00 0x00 0x00 0x00
0x248b888: 0x6c 0xa8 0x78 0xb7 0x00 0x00 0x00 0x00
0x248b890: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x248b898: 0xa8 0x0e 0xd3 0xb7 0x3c 0xa2 0xfa 0xb7
0x248b8a0: 0xff 0xff 0xff 0xff 0x65 0x00 0x00 0x00
0x248b8a8: 0xc0 0x0e 0xd3 0xb7 0x38 0x9c 0xc8 0xb7
0x248b8b0: 0xf4 0x9b 0x04 0x08 0xe4 0x9c 0x04 0x08
0x248b8b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x248b8c0: 0x03 0x00 0x00 0x00 0xbe 0x80 0x79 0xb7
0x248b8c8: 0x58 0x80 0x79 0xb7 0x54 0xa9 0x78 0xb7
0x248b8d0: 0xf8 0xb8 0x48 0x02 0x00 0x00 0x00 0x00


Поиск такой строки в Интернете поначалу не принес каких-либо полезных результатов, но потом обнаружилась одна ссылка на http://www.opensource.apple.com/ (к сожалению, уже не рабочая) со следующим фрагментом:

// This is the exception class we report -- "GNUCC++\0".
const _Unwind_Exception_Class __gxx_exception_class
= ((((((((_Unwind_Exception_Class) 'G'
        << 8 | (_Unwind_Exception_Class) 'N')
       << 8 | (_Unwind_Exception_Class) 'U')
      << 8 | (_Unwind_Exception_Class) 'C')
     << 8 | (_Unwind_Exception_Class) 'C')
    << 8 | (_Unwind_Exception_Class) '+')
    << 8 | (_Unwind_Exception_Class) '+')
  << 8 | (_Unwind_Exception_Class) '\0');


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

(gdb) thread 20
[Switching to thread 20 (process 27635)]#0  0xb7e87921 in dl_iterate_phdr () from /lib/libc.so.6
(gdb) where
#0  0xb7e87921 in dl_iterate_phdr () from /lib/libc.so.6
#1  0x0804e837 in _Unwind_Find_FDE (pc=0xb782409c, bases=0xb70209b4) at ../../gcc/unwind-dw2-fde-glibc.c:283
#2  0x0804c950 in uw_frame_state_for (context=0xb7020960, fs=0xb7020860) at ../../gcc/unwind-dw2.c:903
#3  0x0804cfbf in _Unwind_RaiseException_Phase2 (exc=0xbfde3f38, context=0xb7020960) at ../../gcc/unwind.inc:43
#4  0x0804d397 in _Unwind_Resume (exc=0xbfde3f38) at ../../gcc/unwind.inc:220
#5  0xb78f82b0 in Uuid::Uuid () from /home/'work/lib32/libourlibrary.so
#6  0xb782409d in ...


После этого мы начали смотреть внимательнее на исходный код, где вызывалось исключение, и предположили, что проблема лежит в операторе placement new, который использовался в данном участке кода. Простой тест подтвердил предположение — создание исключения в конструкторе объекта, вызванном из оператора placement new, приводил к бесконечному циклу и взрывной утечке памяти. Тест был простой — симуляция исключительной ситуации в проблемном участке кода, но к сожалению, совсем не простой в отрыве от контекста нашего приложения — небольшие тестовые программы проблему не воспроизводили. Так же оказалось, что данная проблема присутствует только в версии компилятора, которую мы использовали, то есть в gcc 3.4 проблема отсутствовала. На радостях, что конкретная проблема, уже обошедшаяся в копеечку, решена, дальнейшее расследование свернули.

Не совсем утечка

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

Программа работала под Solaris, под нагрузкой, и использование памяти росло резкими скачками по 10-100 MB, при этом достаточно редко — временами раз в 2-3 дня, а временами 2-3 раза в день. Рано или поздно память, используемая процессом, вырастала до 2+ GB. При этом даже при снижении нагрузки до нуля, используемая память (значения RSS и VSS) никогда не уменьшалась.

Непосредственный доступ к серверам получить было нельзя (для настройки libumem, к примеру), а воспроизвести проблему в QA не получалось. Хорошо и то, что удалось получить обрезанный core dump файл — снимок памяти процесса. Но анализ core dump файла практически ничего не дал — стек вызовов показывал падение при выделении памяти из-за нехватки оной. При этом бОльшая часть памяти не использовалась — почти весь core dump файл был занят пустыми страницами по 4 KB, забитыми нулями.

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

При обновлении системы, одна из клиентских программ перестала посылать часть сообщений, предназначенных для мониторинга системы. По случайности, отсутствующие сообщения сигнализировали о закрытии транзакций, и они должны были чистить внутренние буферы. Соответственно, при повышении нагрузки, начинали накапливаться буферизованные сообщения, ждущие пары. По случайному совпадению, в данном случае сообщения были нетипично большими (десятки килобайт вместо сотен байт), и количество клиентских процессов было относительно велико (несколько десятков вместо обычных 2-5). Но на самом деле все эти параметры великолепно укладывались в ограничения, поддерживаемые системой (при условии нормального функционирования). И, хотя программа не ограничивала размер внутренних буферов, но она поддерживала механизм для очистки “потерянных” транзакций — все неполные сообщения старше 10 минут автоматически удалялись. И как раз этот счетчик всегда показывал 0 из-за ошибки — механизм был предназначен для практически невероятной исключительной ситуации, практически никогда не задействован, и недостаточно проверен. Сама очистка исправно работала, но была недостаточно эффективна из-за большой нагрузки на память, вызванной большим количеством клиентов и большим размером нестандартных сообщений. А диагностика проблемы оказалась сильно затруднена из-за неисправного счетчика в статистике.

Почему-же память не возвращалась при снижении нагрузки? Очень просто — стандартный диспетчер памяти процесса в Solaris только увеличивает адресное пространство процесса, и резервирует освобождаемую процессом память для повторного использования, оставляя страницы «занятыми» процессом с точки зрения стороннего наблюдателя.

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

Решение было простым даже до исправления проблемных клиентов и защиты от переполнения буферов — возраст “старых” транзакций был временно ограничен 30 секундами, и этого было вполне достаточно для своевременной вычистки буферов при данной нагрузке, с большим запасом. Но диагностика и поиск неисправности отняли немалое время, главным образом из-за некорректной статистики в логах.

Вместо эпилога

У кого были интересные случаи в практике — пишите!
Share post

Similar posts

Comments 40

    +1
    Уважаемые, не сочтите за спам, но с чего начать изучаение C++?
      +14
      Логичное завершение недели ненависти к C++ на хабре)))

      Я уже не помню с чего начинал и сейчас С++ почти не использую. Но видимо лучшими будут книги Страуструпа и Шилдта. Первого я читал по диагонали уже с определёнными знаниями, а на второго начал поглядывать когда необходимость в изучении плюсов уже отпала, так что возможно будут советы и лучше.
        +2
        Я учился по книге стивена Прата. Очень доступно и подробно описано, включая тонкие мометы использования шаблонов и множественного наследования. Также в этой книге есть множество примеров и задач для самостоятельного решения (по-моему, даже с решениями).
          0
          Стивена Прата, регистр подвел =)
        • UFO just landed and posted this here
        0
        С классики разумеется!

        Бьярн Страустрап. Введение в язык Си++
          +10
          эээ вы серьезно? Страустрап пишет совершенно нечитаемо да еще и введением то о чем он пишет назвать сложно.

          Есть хорошая серия свободных книг: Thinking in <lang-name>
          в вашем случае Thinking in C++ — автор Брюс Эккель. В русском варианте переведена как Философия Си++
          Там две книги — первая как раз для изучающих язык
            +1
            Страуструп пишет прекрасно, но изучать с него си это как отправить мальчика лепящего из бумаги самолётики собирать Су-27.
            Начинать, всё таки советую с Шилдта, хотя каждому своё.
            Да, ещё лучше понять зачем Вам нужен именно С++, и только если Вы на 100% уверены что Вам нужен именно этот язык, принимайтесь за изучение.
              0
              +1 к Эккелю, великолепный старт.
                0
                Но надо иметь в виду, что книга довольно старая и в ней присутствует несколько фактов, которые уже не актуальны или ложны. Например, необходимость вложенную объявлять структуру как дружественную, чтобы иметь доступ к её членам (том1, стр. 201), использование
                enum
                вместо
                static const
                в классах (том 1, стр. 266).
              +3
              С С!
              • UFO just landed and posted this here
              +1
              С «Hello, World»!

              А если серьезно, то сначала нужно определится с задачей — для чего — Сдать сессию? Переквалифицироваться? Добавить еще один скилл? Просто для фана?
                +2
                Скорее третий вариант.
                  +1
                  Тогда начинать надо с авторов, рекомендованных выше, а продолжать:

                  Андрей Александреску (англ. Andrei Alexandrescu) Современное проектирование на С++: Обобщенное программирование и прикладные шаблоны проектирования = Modern C++ Design: Generic Programming and Design Patterns Applied.

                  Герб Саттер (англ. Herb Sutter) Решение сложных задач на С++. — Москва: Вильямс, 2008

                  И, конечно, решать конкретные задачи — проще всего, если что-то по работе нужно (и разрешают заниматься самообразованием)
                    0
                    Вот советовать Александреску, на мой взгляд, очень вредный совет, на людей без реального опыта плюсов он иногда оказывает вредное воздействие, ведущее к написанию «write only» кода…

                    Я бы советовал Александреску читать тем, кто уже знает как работают шаблоны, и хочет углубить знание и трюки…
                      +1
                      Полностью согласен! Мне кажется, Александреску будет вреден в большинстве случаев.
                        +1
                        У него есть вполне милая книжка «Стандарты программирования на С++», в соавторстве с Саттером. Ее вполне можно читать, она местами достаточно полезна. И не травмирует психику, как «Современное проектирование..».
                          0
                          Думаю для новичка эта книга легче усвоится: Брайан Керниган, Роб Пайк Практика программирования.
                          Имею обе на практике студенты-новички отдавали предпочтение Кернигану и Пайку.
                          Стиль изложения более близкий начинающему.
                            +1
                            Само собой никто не предлагает начинать с Саттера и Александреску. У этих книг вообще нет задачи объяснить основы. Про них мы говорили в контексте «продолжать».
                  0
                  А если несколько пунктов из Вашего списка одновременно? )
                    +2
                    Некоторые ветвления могут привести к тому, что вместо С++ выгодней изучать другой язык — например C# :) Недавно я общался с head hunter-ом в моем регионе, и он сказал, что работы на С++ становится меньше (но если есть опыт, то найти не проблема, разве только времени больше займет). Зато оплачивается лучше Java и .NET.

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

                    Для самообучения дома — можно скачать Visual Studio Express, и написать игру Жизнь, к примеру…
                      0
                      Спасибо за дельный совет! Java уже в копилке, но останавливаться нельзя :)
                        0
                        Я с С++ пришел в Java через JNI — обратный путь тоже вернен.

                        Хотя это очень частный случай — у меня было много работы по интеграции native кода с managed (Java, .NET) и разного рода инструментации.
                          +2
                          А вам какие задачи нравитс решать? Если это был дельный совет, то, похоже, гуи. Если сервера, то учите лучше Erlang. Для практичного фана лучше не найти, но и работа потихоньку появляется.
                            0
                            Глаза, на самом деле, разбегаются от обилия языков. Я, похоже, поддался стереотипу, состоящему в том, что «знать С++ престижно». Боюсь только что спрос на Erlang в нашем краю возникнет не скоро. Но всё равно спасибо)
                              –4
                              А GUI на С++ лучше не стоит писать — рынок маленький. Там как раз .NET рулит.
                                +1
                                Gtk#, уточняйте. А то падаваны по вашему совету пойдут клепать одноплатформенные без хорошей на то причины софтины.
                                  +1
                                  С Qt, видимо, не сталкивались?
                                    –1
                                    Я лично крайне редко последние 5 лет сталкивался с разработкой GUI на С++… всё либо .NET, либо Java (для кросс-ллатформенного GUI). А C/C++ только на server-side или для системного программирования. Но это, наверное, просто кому как повезёт :)

                                    Про QT я, естественно, знаю… чтоб не соврать только… первый раз ее году в 93-ем пробовал — такая куча дискет и суперские демки были…

                                    А про GTK# не в курсе, забавно…
                        +1
                        Представляю сколько это вам нервов стоило. Молодцы, что справились)))
                          +2
                          Последний случай («не совсем утечка») мы обнаружили ровно вчера во фронтенде отдающим аватары для агента@mail.ru. На редких пиках активности получаем больше десяти тысяч одновременных соединений. Память под буферы чтения/записи, которую мы выделяем, не возвращается системе, «застревая» в границах brk. Процесс пухнет до полутора гигабайт и остаётся на этой отметке.
                            0
                            Я так думаю, что единственный нормальный способ с этим бороться, это порождать дочерние процессы как в Apache и рециклить (перезапускать) их периодически. На солярке еще libumem должен проде помогать — судя по паре постов в Интернете, он умеет память отдавать (там mmap() используется вместо sbrk()).
                              0
                              С другой стороны, организация работы с дочерними процессами как в Apache, это ужасный геморрой — архитектура сильно усложняется.

                              С третьей, вот IPlanet процессы ресайклить не умеет, и зачастую имеет проблемы с памятью из-за кривых плагинов. Наверное, его только малая распространенность и спасает :)
                                0
                                Это недостаток аллокатора. Я тоже думаю, что можно поменять аллокатор на использующий mmap.

                                В целом это не является большой проблемой, если сервер запущен на специально выделенной под него тачке. У нас как раз такой случай.
                                  0
                                  надо посмотреть какой в nginx используется он вроде умеет ужиматься обратно
                              +2
                              Всегда боялся C++ в highload как огня, почитав вас думаю что не зря :)
                              Поскольку у нас везде чистый С то как правило сталкиваемся со случаями 3-его типа, как верно заметил коллега выше.

                              Из моей практики самый интересный с долгим расследованием был следующий:
                              Проксирующие nginx на проекте новости@mail.ru с нашим рекламным модулем иногда отжирали память цеплялись за своп и моментально уходили в нопинг задыхаясь под нагрузкой.

                              Пытались найти утечку очень долго: снимали coredump в момент перед наступлением ПП, искали утечки valgrind, ничего не помогало. В какой-то момент смогли проверить гипотезу — снятие нагрузки на уже распухшем сервере моментально приводило его в норму по занятой памяти. Значит как таковых утечек в модуле не было в чем же была проблема оставалось непонятно.

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

                              Также надо сказать что проблема стала проявляться когда вместо двух проксирующих серверов временно оставили один на что в начале работы по проблеме не обратили внимания.
                                +2
                                С Си у вас будут те же проблемы. Для больших проэктов приходится для модульности чисто чтобы не потонуть в сложности проэкта вводить какую-то аналогию объектной модели, а тут встретите все те же проблемы только в большей мере потому что С++ стандартные возможности уже лучше отлажены и бага вилезает уж в очень особенных случаях, а ваше придётся еще проверить и в обычных. Да и кросплатформенная разработка как в автора наверное на любом языке привносит свои проблемы, даже у JVM на разных платформах хватает своих недокументированых особеностей реализации.
                                  0
                                  Полностью согласен — на С удобно писать плагины небольшого размера с минимумом зависимостей, но большие проекты на нем писать грустно.

                              Only users with full accounts can post comments. Log in, please.