2010-07-09 18 views
9

Esta es una pregunta más teórica sobre las macros (creo). Sé que las macros toman el código fuente y producen código objeto sin evaluarlo, lo que permite a los programadores crear estructuras sintácticas más versátiles. Si tuviera que clasificar estos dos macro sistemas, diría que era la macro "Estilo C" y la macro "Estilo Lisp".¿Cómo hace un lenguaje macro habilitado para hacer un seguimiento del código fuente para la depuración?

Parece que las macros de depuración pueden ser un poco complicadas porque, en tiempo de ejecución, el código que realmente se ejecuta difiere de la fuente.

¿Cómo hace el depurador un seguimiento de la ejecución del programa en términos del código fuente preprocesado? ¿Existe un "modo de depuración" especial que deba configurarse para capturar datos adicionales sobre la macro?

En C, puedo entender que establezca un modificador de tiempo de compilación para la depuración, pero ¿cómo lo haría un lenguaje interpretado, como algunas formas de Lisp?

Disculpame por no haber probado esto, pero la herramienta de lisp requiere más tiempo de lo que tengo que gastar para averiguarlo.

Respuesta

3

No creo que haya una diferencia fundamental en las macros de "estilo C" y "estilo Lisp" en la forma en que se compilan. Ambos transforman la fuente antes de que el compilador la vea correctamente. La gran diferencia es que las macros de C usan el preprocesador C (un lenguaje secundario más débil que es principalmente para la sustitución de cadenas simples), mientras que las macros de Lisp están escritas en Lisp (y por lo tanto pueden hacer cualquier cosa).

(Como nota aparte: no he visto un Lisp no compilado desde hace tiempo ... desde luego no desde el cambio de siglo. Pero en todo caso, interpretarlo parecería facilitar el problema de la depuración de macros, no es más difícil, ya que tiene más información.)

Estoy de acuerdo con Michael: no he visto un depurador para C que maneje macros en absoluto. El código que usa macros se transforma antes de que ocurra algo. El "debug" mode for compiling C code generalmente significa que almacena functions, types, variables, filenames, and such - No creo que ninguno de ellos almacene información sobre macros.

  • Para los programas de depuración que utilizan macros, Lisp es más o menos la misma como C aquí: el depurador ve el código compilado , no la macro aplicación. Normalmente, las macros son simples y depuradas de forma independiente antes de su uso, para evitar la necesidad de esto, al igual que C.

  • Para depurar las macros mismos, antes de ir y lo utiliza en alguna parte, Lisp tiene características que hacen que esto sea más fácil que en C, por ejemplo, de la réplica y macroexpand-1 (aunque en C existe obviamente una forma de macroexpandir un archivo completo, completamente, en una vez). Puede ver el antes y después de una macroexpansión, directamente en su editor, cuando escriba .

No puedo recordar un momento me encontré con una situación en la depuración en una definición de macro en sí habría sido útil. O bien es un error en la definición de la macro, en cuyo caso macroexpand-1 aísla el problema inmediatamente, o es un error por debajo de eso, en cuyo caso las instalaciones normales de depuración funcionan bien y no me importa que ocurra una macroexpansión entre dos fotogramas de mi llamada apilar.

1

No sé acerca de las macros lisp (que sospecho que son bastante diferentes de las macros C) o la depuración, pero muchos - probablemente la mayoría - depuradores C/C++ no manejan la depuración a nivel de fuente de las macros C preprocesador particularmente bien .

Generalmente, los depuradores de C/C++ no 'entran' en la definición de la macro. Si una macro se expande en varias instrucciones, entonces el depurador normalmente permanecerá en la misma línea de origen (donde se invoca la macro) para cada operación de "paso" del depurador.

Esto puede hacer que las macros de depuración sean un poco más dolorosas de lo que podrían ser, otra razón para evitarlas en C/C++. Si una macro se está portando mal de una manera verdaderamente misteriosa, me colocaré en el modo de ensamblaje para depurarla o expandir la macro (ya sea manualmente o usando el interruptor del compilador). Es bastante raro que tengas que llegar a ese extremo; Si está escribiendo macros que son tan complicados, probablemente esté tomando el enfoque equivocado.

1

Por lo general, en C la depuración a nivel de fuente tiene granularidad de línea (comando "siguiente") o granularidad a nivel de instrucción ("paso hacia adentro"). Los macroprocesadores insertan directivas especiales en la fuente procesada que permiten al compilador mapear secuencias compiladas de instrucciones de la CPU para las líneas de código fuente.

En Lisp no existe una convención entre macros y compilador para rastrear el código fuente a la correlación de código compilada, por lo que no siempre es posible hacer un solo paso en el código fuente.

La opción obvia es hacer un solo paso en el código macro expandido. El compilador ya ve la versión final y ampliada del código y puede rastrear el código fuente a la asignación de código de máquina.

Otra opción es utilizar el hecho de que las expresiones de lisp durante la manipulación tienen identidad. Si la macro es simple y simplemente desestructura y pega el código en la plantilla, algunas expresiones de código expandido serán idénticas (con respecto a la comparación EQ) a las expresiones que se leyeron desde el código fuente. En este caso, el compilador puede asignar algunas expresiones del código expandido al código fuente.

2

Realmente debe considerar el tipo de soporte que Racket tiene para depurar código con macros. Este soporte tiene dos aspectos, como menciona Ken. Por un lado está el problema de las macros de depuración: en Common Lisp, la mejor manera de hacerlo es simplemente expandir los formularios macro manualmente. Con CPP, la situación es similar pero más primitiva: correría el código solo a través de la expansión de CPP e inspeccionaría el resultado. Sin embargo, ambos son insuficientes para macros más implicadas, y esta fue la motivación para tener un macro debugger en Racket: muestra los pasos de expansión de sintaxis uno por uno, con indicaciones adicionales basadas en la interfaz gráfica de usuario para cosas como identificadores enlazados, etc.

En el lado de usando macros, Racket siempre ha sido más avanzado que otras implementaciones de Scheme y Lisp. La idea es que cada expresión (como un objeto sintáctico) es el código más datos adicionales que contiene su ubicación de origen. De esta forma, cuando un formulario es una macro, el código expandido que tiene partes provenientes de la macro tendrá la ubicación de origen correcta, a partir de la definición de la macro y no de su uso (donde las formas no están realmente presentes). Algunas implementaciones de Scheme y Lisp implementarán un límite de esto usando la identidad de subformularios, como se mencionó en dmitry-vk.

+0

Pero mapear código compilado de nuevo a macro no resuelve el siguiente problema: cuando macro genera código basado en entrada declarativa (por ejemplo, macro que genera analizador de gramática sin contexto) sería muy útil durante la depuración para poder encontrar qué parte de la entrada está siendo "activa" (por ejemplo, qué regla de gramática se está emparejando si eso es posible). Eso requeriría que el macro escritor dijera explícitamente qué partes del mapa de código generado corresponden a qué partes de la entrada de macro. ¿Las macros Raqueta tienen esa habilidad? De lo contrario, es equivalente al código de depuración (parcialmente) expandido. –

+0

"De lo contrario, es equivalente al código de depuración (parcialmente) expandido". No creo que esté equivocado en esta oración. Por favor ignórelo. –

+0

dmity-vk: Derecha - la forma especial 'sintaxis' en Racket consiste básicamente en combinar fragmentos de código del usuario macro con fragmentos de código de la macro misma, y ​​asegurarse de que la ubicación de origen en los formularios resultantes correcto en todas las formas. –

0

La respuesta simple es que es complicado ;-) Hay varias cosas diferentes que contribuyen a poder depurar un programa, y ​​aún más para rastrear macros.

En C y C++, el preprocesador se usa para expandir macros e incluye en el código fuente real. Los nombres de archivo de origen y los números de línea se rastrean en este archivo de origen expandido utilizando las directivas #line.

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

Cuando un programa C++ C o se compila con la depuración habilitada, el ensamblador genera información adicional en el archivo de objeto que rastrea líneas de código, nombres de símbolos, forma de descriptores, etc.

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

El sistema operativo tiene características que hacen posible que un depurador se conecte a un proceso y controle la ejecución del proceso; pausing, single steppping, etc.

Cuando se adjunta un depurador al programa, traduce la pila de proceso y el contador de programa de nuevo a forma simbólica buscando el significado de las direcciones del programa en la información de depuración.

Los lenguajes dinámicos se suelen ejecutar en una máquina virtual, ya sea un intérprete o un bytecode VM. Es la VM que proporciona enlaces para permitir que un depurador controle el flujo del programa e inspeccione el estado del programa.

Cuestiones relacionadas