![]() |
1
3
我认为“c样式”和“lisp样式”宏在编译方式上没有根本区别。两种方法都在编译器正确看到源代码之前进行转换。最大的区别是,C的宏使用C预处理器(一种较弱的辅助语言,主要用于简单的字符串替换),而Lisp的宏是用Lisp本身编写的(因此可以做任何事情)。 (旁白:我有一段时间没有看到非编译的Lisp了……当然不是世纪之交。但是,如果有什么不同的话,被解释似乎会使宏调试问题更容易,而不是更难,因为您有更多的信息。) 我同意迈克尔的观点:我还没有看到一个C的调试器可以处理宏。使用宏的代码在发生任何事情之前都会被转换。这个 "debug" mode for compiling C code 一般只意味着它存储 functions, types, variables, filenames, and such --我认为它们中没有任何一个存储有关宏的信息。
我不记得有什么时候我遇到过调试的情况
进入之内
宏定义本身是有用的。要么是宏定义中的错误,在这种情况下
|
![]() |
2
3
在 LispWorks 开发人员可以使用 Stepper tool . LispWorks提供了一个步进器,可以在其中一步完成整个 macro expansion process . |
![]() |
3
2
你真的应该调查一下 Racket 用于调试宏代码。正如Ken提到的,这种支持有两个方面。一方面是调试宏的问题:在普通的Lisp中,最好的方法是手动扩展宏窗体。对于cpp,情况类似但更原始——您只需要通过cpp扩展运行代码并检查结果。但是,这两种方法都不足以支持更复杂的宏,这也是 macro debugger 在racket中——它一个接一个地向您显示语法扩展步骤,并为绑定标识符等提供基于GUI的指示。 站在一边 使用 宏、Racket始终比其他方案和Lisp实现更高级。其思想是每个表达式(作为语法对象)都是代码加上包含其源位置的附加数据。这样,当表单是宏时,包含来自宏的部分的扩展代码将具有正确的源位置——从宏的定义而不是从其使用(表单实际上不存在)开始。正如dmitry vk所提到的,一些方案和lisp实现将使用子表单的标识来实现这一限制。 |
![]() |
4
1
我不知道LISP宏(我怀疑它可能与C宏相当不同)或调试,但是很多可能是大多数C/C++调试器不特别处理C预处理器宏的源级调试。 通常,C/C++调试器不进入宏定义。如果宏扩展为多个语句,那么对于每个调试器的“步骤”操作,调试器通常只停留在同一源行(调用宏的位置)。 这可以使调试宏比他们可能要更痛苦一些——这是避免C/C++中的另一个原因。如果一个宏的行为异常,那么我将进入汇编模式,对它进行调试或展开宏(手动或使用编译器的开关)。要达到这样的极限是非常罕见的;如果编写的宏非常复杂,那么您可能采用了错误的方法。 |
![]() |
5
1
通常在C源代码级调试具有行粒度(“next”命令)或指令级粒度(“step into”)。宏处理器将特殊指令插入到已处理的源代码中,以便编译器将已编译的CPU指令序列映射到源代码行。 在Lisp中,宏和编译器之间不存在跟踪源代码到已编译代码映射的约定,因此不可能总是单步执行源代码。 显而易见的选择是单步执行宏扩展代码。编译器已经看到了代码的最终版本、扩展版本,并且可以跟踪源代码到机器代码的映射。 另一种选择是使用操作期间的lisp表达式具有标识这一事实。如果宏很简单,只需将代码销毁并粘贴到模板中,那么扩展代码的某些表达式(关于eq比较)将与从源代码中读取的表达式相同。在这种情况下,编译器可以将一些表达式从扩展代码映射到源代码。 |
![]() |
6
0
简单的答案是,它是复杂的;-)有几个不同的事情,有助于调试程序,甚至更多的跟踪宏。 在C和C++中,预处理器用于扩展宏并包含到实际的源代码中。使用line指令跟踪扩展源文件中的原始文件名和行号。 http://msdn.microsoft.com/en-us/library/b5w2czay(VS.80).aspx 当C或C++程序被编译并启用调试时,汇编器在跟踪源行、符号名、类型描述符等的目标文件中生成附加信息。 http://sources.redhat.com/gdb/onlinedocs/stabs.html 操作系统的特性使调试器可以附加到进程并控制进程执行;暂停、单步执行等。 当调试器连接到程序时,它通过在调试信息中查找程序地址的含义,将进程堆栈和程序计数器转换回符号形式。 动态语言通常在虚拟机中执行,无论是解释器还是字节码VM。正是VM提供了钩子,允许调试器控制程序流并检查程序状态。 |