Как язык с поддержкой макросов отслеживает исходный код для отладки?


Это более теоретический вопрос о макросах (я думаю). Я знаю, что макросы берут исходный код и производят объектный код, не оценивая его, что позволяет программистам создавать более универсальные синтаксические структуры. Если бы мне пришлось классифицировать эти две макросистемы, я бы сказал, что есть макрос "C style" и макрос "Lisp style".

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

Как работает отладчик отслеживает выполнение программы в терминах предварительно обработанного исходного кода? Существует ли специальный "режим отладки", который должен быть установлен для сбора дополнительных данных о макросе?

В C я могу понять, что вы установили бы переключатель времени компиляции для отладки, но как бы интерпретируемый язык, такой как некоторые формы Lisp, сделал это?

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

6 9

6 ответов:

Я не думаю, что есть принципиальная разница в макросах "C style" и "Lisp style" в том, как они компилируются. Оба преобразуют исходный код до того, как его увидит сам компилятор. Большая разница заключается в том, что макросы C используют препроцессор C (более слабый вторичный язык, который в основном предназначен для простой замены строк), в то время как макросы Lisp написаны на самом Lisp (и, следовательно, могут делать все что угодно).

(в качестве отступления: я давно не видел некомпилированного Lisp ... конечно, не с тех пор, как ... на рубеже веков. Но если уж на то пошло, интерпретация, похоже, сделает проблему отладки макросов проще, а не сложнее, поскольку у вас есть больше информации.)

Я согласен с Майклом: я вообще не видел отладчика для C, который обрабатывает макросы. Код, использующий макросы, преобразуется до того, как что-либо произойдет. Режим "debug" для компиляции C-кода обычно просто означает, что он хранит функции, типы, переменные, имена файлов и т. д. - я не думаю, что какой-либо из них хранит информация о макросах.

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

  • Для отладки макросов сами по себе , прежде чем вы пойдете и используете его где-то, Lisp имеет функции что делает это проще, чем в C, например, репл и macroexpand-1 (хотя в C очевидно, есть способ сделать это. макрорасширение всего файла, полностью, на однажды). Вы можете видеть до и после макрорасширения, прямо в вашем редакторе, когда вы пишете оно.

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

ВLispWorks разработчики могут использовать шаговый инструмент .

LispWorks предоставляет шаговый механизм, в котором можно выполнить полный процесс расширения макроса .

Вам действительно следует изучить тип поддержки, которую Racket имеет для отладки кода с помощью макросов. Эта поддержка имеет два аспекта, как упоминает Кен. С одной стороны, есть проблема отладки макросов: в Common Lisp лучший способ сделать это-просто развернуть макроформы вручную. С CPP ситуация аналогична, но более примитивна - вы запускаете код только через расширение CPP и проверяете результат. Однако и того, и другого недостаточно для более сложных макросов, и это было мотивацией для наличия макро-отладчика в Racket-он показывает вам шаги расширения синтаксиса один за другим, с дополнительными указаниями на основе графического интерфейса для таких вещей, как связанные идентификаторы и т. д.

На стороне , использующей макросы, Racket всегда был более продвинутым, чем другие реализации Scheme и Lisp. Идея заключается в том, что каждое выражение (как синтаксический объект) - это код плюс дополнительные данные, содержащие его исходное местоположение. Таким образом, когда форма является макросом, расширенный код, который содержит части, поступающие из макроса, будет иметь правильное расположение источника - из определения макроса, а не из его использования (где формы на самом деле не присутствуют). Некоторые схемы и реализации Lisp будут реализовывать ограниченное для этого использование идентичности подформ, как упоминал Дмитрий-ВК.

Я не знаю о макросах lisp (которые, как я подозреваю, вероятно, сильно отличаются от макросов C) или отладке, но многие - вероятно, большинство - отладчики C/C++ не особенно хорошо справляются с отладкой макросов препроцессора C на уровне исходного кода.

Как правило, отладчики C / C++ не "шагают" в определение макроса. Если макрос расширяется в несколько операторов, ТО отладчик обычно просто остается на одной исходной строке (где макрос вызывается) для каждого "шага" отладчика операция.

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

Обычно в C отладка на уровне исходного кода имеет линейную детализацию (команда" next") или детализацию на уровне инструкций (команда"step into"). Макропроцессоры вставляют в обрабатываемый источник специальные директивы, которые позволяют компилятору сопоставлять скомпилированные последовательности инструкций процессора со строками исходного кода.

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

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

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

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

В C и C++ препроцессор используется для расширения макросов и включает в себя фактический исходный код. Исходные имена файлов и номера строк отслеживаются в этом расширенном исходном файле с помощью директив #line.

Http://msdn.microsoft.com/en-us/library/b5w2czay (VS.80).aspx

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

Http://sources.redhat.com/gdb/onlinedocs/stabs.html

Операционная система имеет функции, которые позволяют отладчику подключаться к процессу и управлять его выполнением; пауза, один шаг и т. д.

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

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