Я заметил странное поведение GDB 7.5 в Windows. Рассмотрим следующую программу C:
int foo(void){
int i = 5;
return i;
}
int main(int argc, char** argv){
foo();
return 0;
}
При компиляции как классического C, так и C++ команда GDB disass foo
дает один и тот же код сборки:
Dump of assembler code for function foo:
0x00401954 <+0>: push %ebp
0x00401955 <+1>: mov %esp,%ebp
0x00401957 <+3>: sub $0x10,%esp
0x0040195a <+6>: movl $0x5,-0x4(%ebp)
0x00401961 <+13>: mov -0x4(%ebp),%eax
0x00401964 <+16>: leave
0x00401965 <+17>: ret
End of assembler dump.
Однако после вставки точки останова в команду "оставить", например: br *0x00401964
, запустив код до этой строки и пытаясь распечатать переменную i, выполняются исполняемые файлы, созданные путем компиляции его как C и C++ иначе. Исполняемый файл C работает так, как ожидалось, и выводит $i = 5
, а при выполнении исполняемого GDB C++ - "нет символа я в текущем контексте".
Так что просто из любопытства я хотел бы знать, является ли это ошибкой или функцией GDB? Или компилятор (GCC) делает что-то тонко другое, чтобы что-то происходило между строк? Благодарю.
EDIT: Ну, я не думаю, что это правда, что компилятор полностью удалил функцию, потому что нарушение в строке перед "уходом" и печать значения я действительно работает.
Это не ошибка или функция, а побочный эффект оптимизации компилятора. Разборки - это вывод неинтерминированной сборки (i
записывается в стек в foo+6
и перечитывается из стека на один шаг позже в foo+13
).
Хотя вывод сборки C и C++ в этом случае одинаковый, выход символа отладки, однако, немного отличается. Объем i
более ограничен в C++. Я могу только рассуждать о причинах. Я бы предположил, что это связано с тем, что область обзора более сложна в C++ (подумайте о конструкторах, деструкторах, исключении), и поэтому C++ часть gcc более жесткая по областям, чем C-часть gcc.
(Я проверил все на 32-битной сборке, но на 64-разрядной Linux с gcc 4.8 и gdb 7.6. Хотя некоторые детали будут отличаться в Windows, я ожидаю, что общая механика будет одинаковой)
Обратите внимание, что адреса различаются в моем случае.
(gdb) disas foo
Dump of assembler code for function foo:
0x080483ed <+0>: push %ebp
0x080483ee <+1>: mov %esp,%ebp
0x080483f0 <+3>: sub $0x10,%esp
0x080483f3 <+6>: movl $0x5,-0x4(%ebp)
0x080483fa <+13>: mov -0x4(%ebp),%eax
0x080483fd <+16>: leave
0x080483fe <+17>: ret
End of assembler dump.
Технически foo+0
и foo+1
- функция пролога, foo+3
- foo+13
- тело функции, а foo+16
и foo+17
- эпилог функции. Таким образом, только foo+3
to foo+13
представляет код между {
и }
. Я бы сказал, что версия C++ более правильна, говоря, что i
входит в сферу действия до и после тела функции.
Чтобы убедиться, что это действительно вопрос отладочных символов, вы можете сбросить внутренние maintenance print symbols output_file_on_disk
gdb структур отладки с maintenance print symbols output_file_on_disk
. Для C это выглядит так:
block #000, object at 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e int foo(); block object 0x18470d0, 0x80483ed..0x80483ff int main(int, char **); block object 0x18475d0, 0x80483ff..0x804840e section .text block #001, object at 0x18476a0 under 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e typedef int int; typedef char char; block #002, object at 0x18470d0 under 0x18476a0, 1 syms/buckets in 0x80483ed..0x80483ff, function foo int i; computed at runtime block #003, object at 0x18475d0 under 0x18476a0, 2 syms/buckets in 0x80483ff..0x804840e, function main int argc; computed at runtime char **argv; computed at runtime
Хотя это C++
block #000, object at 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e int foo(); block object 0x1a3c0c0, 0x80483ed..0x80483ff int main(int, char**); block object 0x1a3c640, 0x80483ff..0x804840e section .text block #001, object at 0x1a3c720 under 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e typedef int int; typedef char char; block #002, object at 0x1a3c0c0 under 0x1a3c720, 0 syms/buckets in 0x80483ed..0x80483ff, function foo() block #003, object at 0x1a3c050 under 0x1a3c0c0, 1 syms/buckets in 0x80483f3..0x80483fd int i; computed at runtime block #004, object at 0x1a3c640 under 0x1a3c720, 2 syms/buckets in 0x80483ff..0x804840e, function main(int, char**) int argc; computed at runtime char **argv; computed at runtime
Таким образом, символы отладки для кода C++ различают всю функцию (блок № 002) и область действия тела функции (блок # 003). Это приводит к вашим наблюдениям.
(И чтобы увидеть, что это действительно не gdb, просто что-то не так, вы можете даже анализировать двоичный файл с помощью objdump
на Linux или dumpbin
в Windows. Я сделал это на Linux, и действительно это символы отладки DWARF, которые отличаются друг от друга :-))
Это не ошибка или особенность. Компилятору разрешено заменять функционально эквивалентный код и обычно делает это, если он может найти лучший способ сделать что-то. Пример кода эквивалентен тому, что он ничего не делает, поэтому компилятор может удалить его. Это позволяет отладчику ничего не отлаживать, что хорошо, поскольку отладочный код, который ничего не делает, будет пустой тратой времени.
leave
работаетdisass foo
генерирует одинаковый ассемблерный код как на C, так и на C ++, то не означает ли это, чтоfoo
не был скомпилирован? Это может быть не проблема GDB вообще. В зависимости от параметров командной строки C / C ++, C ++ может не производить отладочную информацию.