Отладка C++ в gdb: точки останова на живом примере
Программа падает или ведёт себя не так, а логов мало. Разбираем, как поставить breakpoint в gdb, остановиться в нужный момент и посмотреть, что происходит с переменными в конкретном кадре стека.

Программа собралась, запустилась и упала с Segmentation fault — а где именно, непонятно. Или считает не то число, а вставлять std::cout в каждую функцию долго и грязно. gdb (GNU Debugger) позволяет остановить выполнение в нужной строке, посмотреть значения переменных и пройти код по шагам. Дальше — конкретные приёмы работы с точками останова, которые закрывают большую часть повседневной отладки. Всё проверено на gdb 12.x и GCC 13.x под Linux; на macOS вместо gdb чаще используют lldb с похожим набором команд.
Что нужно до начала
gdb читает отладочную информацию: имена переменных, номера строк, типы. Без неё вы увидите голые адреса вместо имён. Поэтому собирать нужно с флагом -g, а оптимизацию на время отладки лучше отключить.
# сборка с отладочной информацией, без оптимизации
g++ -g -O0 main.cpp -o app
Флаг -O0 важен: при -O2 компилятор переставляет и выкидывает строки, переменные складываются в регистры, и gdb показывает <optimized out> вместо значения. Отлаживать оптимизированный код можно, но новичку это только мешает.
Первая остановка: точка по номеру строки и по функции
Запустите отладчик и передайте ему исполняемый файл:
gdb ./app
Внутри gdb работают команды. Точка останова ставится командой break (сокращённо b):
# остановиться на входе в функцию
(gdb) break main
# остановиться на конкретной строке файла
(gdb) break main.cpp:42
# запустить программу
(gdb) run
После run выполнение дойдёт до первой точки и остановится. gdb покажет номер строки и её текст. Значит, всё сработало: вы стоите перед выполнением этой строки, а не после неё.
Посмотреть список расставленных точек — info breakpoints. Каждая получает номер, по которому её потом удаляют (delete 2) или временно выключают (disable 2 / enable 2).
Шаг за шагом
Когда остановились, четыре команды закрывают почти всё:
next(n) — выполнить текущую строку целиком, не заходя внутрь вызовов функций;step(s) — то же, но зайти внутрь вызванной функции;continue(c) — продолжить до следующей точки останова;finish— доработать текущую функцию до конца и вернуться в вызывающую.
Разница между next и step — главная развилка новичка. Если строка result = compute(x); вас не интересует изнутри, жмите next. Если баг в compute — step.
Смотрим значения
Остановились — теперь надо понять, что в переменных. Команда print (p) печатает выражение:
(gdb) print counter
$1 = 17
# работают выражения и обращение к полям
(gdb) print user.age
(gdb) print arr[3]
# посмотреть все локальные переменные разом
(gdb) info locals
# аргументы текущей функции
(gdb) info args
Чтобы не печатать переменную вручную на каждом шаге, добавьте её в постоянный показ командой display: display counter — и значение будет выводиться автоматически после каждого next или step.
Отладчик не заменяет понимания кода. Он показывает, что происходит на самом деле, — а расходится ли это с тем, что вы задумали, решаете вы.
Условные точки останова — когда цикл крутится тысячу раз
Проблема проявляется на 743-й итерации цикла. Останавливаться руками 742 раза бессмысленно. Точке останова задаётся условие:
# остановиться на строке 42, только когда i == 743
(gdb) break main.cpp:42 if i == 743
# добавить условие к уже существующей точке номер 1
(gdb) condition 1 ptr == nullptr
Второй вариант — точка на срабатывание. Пропустить N попаданий и остановиться только потом:
# проигнорировать точку 1 первые 100 раз
(gdb) ignore 1 100
Watchpoint: остановиться, когда переменная изменилась
Иногда неизвестно, где переменная портится, — известно только, что к концу она не та. Здесь помогает watch: выполнение прервётся в тот момент, когда значение поменяется, независимо от того, какая строка это сделала.
# прервать выполнение при любом изменении balance
(gdb) watch balance
Watchpoint отлично ловит порчу памяти и неожиданную запись, но следит только за переменными в текущей области видимости: когда переменная выходит из scope, gdb сам его снимет. Аппаратные watchpoint'ы быстрые, программные (когда железо не поддерживает) сильно замедляют выполнение.
Стек вызовов: как сюда попали
После падения или на точке останова команда backtrace (bt) показывает цепочку вызовов — кто кого вызвал:
(gdb) bt
#0 compute (x=0) at main.cpp:12
#1 process (data=...) at main.cpp:30
#2 main () at main.cpp:45
Каждая строка — кадр стека (frame) со своим номером. Чтобы посмотреть локальные переменные не текущей, а вызывающей функции, переключитесь в её кадр командой frame 1 (или up / down для перехода на кадр вверх и вниз). После переключения info locals и print покажут переменные уже этого кадра — так вы поднимаетесь по стеку и видите, с какими аргументами вас позвали.
Команды, которые стоит запомнить
| Команда | Сокр. | Что делает |
|---|---|---|
| break файл:строка | b | Точка останова на строке |
| break функция if условие | — | Условная точка |
| run | r | Запустить программу |
| continue | c | Продолжить до следующей точки |
| next | n | Шаг, не заходя в функции |
| step | s | Шаг с заходом внутрь |
| print выражение | p | Показать значение |
| backtrace | bt | Стек вызовов |
| watch переменная | — | Прервать при изменении |
| info locals | — | Все локальные переменные |
Если не получилось
No symbol table is loaded. Use the "file" command.— программа собрана без-g. Пересоберите с флагом-g.- gdb показывает
<optimized out>вместо значения — включена оптимизация. Соберите с-O0. - Точка останова не срабатывает, gdb пишет
Breakpoint 1 ... pending— вы указали строку в файле, который ещё не загружен (например, из динамической библиотеки). Точка станет активной, когда код загрузится; для этого ответьтеyна вопрос gdb про pending. Cannot access memory at address 0x0приprint— обращаетесь к разыменованию нулевого указателя. Это и есть ваш баг: проверьте указатель до разыменования.- На macOS
gdbругается на подпись или права — gdb под macOS требует code-signing. Проще взятьlldb, который идёт с Xcode; команды близки:breakpoint set,run,frame variable.
Что дальше
Когда базовые точки останова освоены, посмотрите в сторону: анализа падений по core dump (gdb ./app core) — отладка после факта, без повторного запуска; TUI-режима gdb (gdb -tui или Ctrl-X A), где виден исходник рядом с командной строкой; связки с санитайзерами — сборка с -fsanitize=address ловит выход за границы массива и use-after-free ещё до того, как вы дойдёте до gdb. Отладка утечек памяти через Valgrind — отдельная тема, здесь мы её не разворачиваем.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Debugging with GDB — GNU Manual, GCC — Options for Debugging Your Program
Частые вопросы
У меня gdb другой версии, команды те же?
Можно ли отлаживать программу, собранную с оптимизацией -O2?
Точка останова не срабатывает, хотя строка точно выполняется — что не так?
Как отладить программу, которая уже упала, если не хочу запускать заново?
gdb под macOS не запускается или ругается на права — что делать?
Как остановиться на изменении конкретной переменной, а не на строке?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.