sirve que para nivel maquina lenguaje historia ensamblador ejemplos bajo alto c lisp macros language-implementation

que - ¿Cómo hace un lenguaje macro habilitado para hacer un seguimiento del código fuente para la depuración?



lenguaje maquina (6)

Esta es una pregunta más teórica sobre 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".

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

¿Cómo hace el depurador para realizar 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 configuró un conmutador de tiempo de compilación para la depuración, pero ¿cómo lo haría un lenguaje interpretado, como algunas formas de Lisp?

Discúlpame por no haber probado esto, pero la cadena de herramientas de lisp requiere más tiempo de lo que tengo que gastar para descubrirlo.


Debería considerar el tipo de soporte que tiene Racket 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 depurador de macros en Racket: muestra los pasos de expansión de sintaxis uno por uno, con indicaciones adicionales basadas en gui para cosas como identificadores de límite, etc.

Del lado del uso de 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.



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 se compila un programa C o C ++ con la depuración habilitada, el ensamblador genera información adicional en el archivo objeto que rastrea las líneas fuente, nombres de símbolos, descriptores de tipo, 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; pausa, paso simple, etc.

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

Los lenguajes dinámicos normalmente se ejecutan en una máquina virtual, ya sea un intérprete o una máquina virtual de código de bytes. Es la VM que proporciona enlaces para permitir que un depurador controle el flujo del programa e inspeccione el estado del programa.


No creo que haya una diferencia fundamental en las macros de "estilo C" y "estilo Lisp" en cómo 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 comentario adicional: no he visto un Lisp no compilado en un tiempo ... desde luego no desde el cambio de siglo. Pero si algo se interpreta, parecería que hace que el problema de la depuración macro sea más fácil, no más difícil, ya que usted tiene más información alrededor.)

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 modo de "depuración" para compilar el código C en general solo significa que almacena funciones, tipos, variables, nombres de archivos, etc. - No creo que ninguno de ellos almacene información sobre macros.

  • Para depurar programas que usan macros , Lisp es más o menos lo mismo que C aquí: su depurador ve el código compilado, no la macro. Por lo general, las macros se mantienen simples y se depuran de forma independiente antes de su uso, para evitar la necesidad de esto, al igual que C.

  • Para depurar las macros, antes de ir y usarlo en alguna parte, Lisp tiene características que lo hacen más fácil que en C, por ejemplo, el repl y macroexpand-1 (aunque en C obviamente hay una forma de macroexpandir un archivo completo, completamente , En seguida). Puedes ver el antes y el después de una macroexpansión, directamente en tu editor, cuando lo escribes.

No puedo recordar ninguna ocasión en que me encontré con una situación en la que la depuración de una definición de macro hubiera sido útil. O bien es un error en la definición de 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 marcos de mi pila de llamadas.


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

En general, 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 de otra manera, una razón más 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.


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 las macros y el 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 usar 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.