使用SBCL与SLIME调试Common Lisp函数时单步执行异常
find-3rd-largest 函数时的单步执行异常 我在调试下面的find-3rd-largest函数,希望从(break)处开始像其他语言那样逐步执行表达式。我用C-u C-c C-c(即C-u M-x slime-compile-defun)、C-M-x(slime-eval-defun)或C-c C-k(slime-compile-and-load-file)编译函数后,在REPL中执行(find-3rd-largest '(2 1 0 6 5 3 4))。正如预期,REPL暂停并启动SLDB,我可以输入x(sldb-next)或s(sldb-step)进行单步,但SLDB仅执行2~3次操作后就结束,无法继续遍历后续代码。我想知道是不是部分代码不支持单步调试,测试环境为SBCL 2.5.10.roswell和SBCL 2.1.11.debian。
(defun find-3rd-largest (nums) ;; [可跳过说明] ;; 这个函数和《On Lisp》一书中的`nthmost`宏在查找第三大值时的逻辑完全一致。 ;; 以下表达式会得到相同结果: ;; > (find-3rd-largest '(2 1 0 6 5 3 4)) ;; 4 ;; > (nthmost 2 '(2 1 0 6 5 3 4)) ; 2代表索引位,对应第三大的元素 ;; 4 (declare (optimize (speed 0) (safety 3) (space 0) (debug 3))) (let ((g7 nums) (g1 nil) (g2 nil) (g3 nil)) (unless (< (length g7) 3) (break) (let ((g6 (pop g7))) (setq g1 g6)) (let ((g5 (pop g7))) (if (> g5 g1) (setq g2 g1 g1 g5) (setq g2 g5))) (let ((g4 (pop g7))) (if (> g4 g1) (setq g3 g2 g2 g1 g1 g4) (if (> g4 g2) (setq g3 g2 g2 g4) (setq g3 g4)))) (dolist (g8 g7) (if (> g8 g1) (setq g3 g2 g2 g1 g1 g8) (if (> g8 g2) (setq g3 g2 g2 g8) (if (> g8 g3) (setq g3 g8) nil)))) g3)))
问题原因与解决方法
编译方式需严格对应调试要求
不要用C-M-x解释执行函数,必须用C-c C-k编译整个文件,或C-u C-c C-c编译单个函数——只有编译后的代码才能保留完整的调试断点信息。同时检查declare中的(debug 3)是否生效,可在REPL执行(describe #'find-3rd-largest)查看编译参数。正确选择SLDB单步命令
s(sldb-step)会尝试进入子函数(比如pop、length这些内置函数),但SLDB默认跳过系统代码,导致看起来像是“跳步”。改用n(sldb-next)执行当前顶层表达式并停在下一个表达式,对于dolist循环,n会停在每次循环的开头,能完整遍历每个元素的处理逻辑。循环体的调试优化
如果dolist循环仍无法单步进入,可以将循环内的逻辑拆成独立函数,比如:(defun update-top3 (g1 g2 g3 val) (cond ((> val g1) (values val g1 g2)) ((> val g2) (values g1 val g2)) ((> val g3) (values g1 g2 val)) (t (values g1 g2 g3))))然后在
dolist中调用该函数:(dolist (g8 g7) (multiple-value-setq (g1 g2 g3) (update-top3 g1 g2 g3 g8)))拆分后就能轻松单步进入循环体的逻辑。
排除全局优化覆盖
确保没有全局优化设置覆盖局部的optimize声明,可在REPL执行(policy-quality 'debug)查看全局调试级别,若高于3则没问题,若低于则需要调整全局编译参数。
内容的提问来源于stack exchange,提问作者dhnam

