SBCL 1.3.1
总之,a
是一个列表,'(7)
,b
通过setq
设置为相同的列表。值将附加到b
。列表c
设置为追加后的预期结果,即'(7 1)
。然后将a
与c
进行比较,并正确比较true。但是,当a
通过(equal a '(7 1))
进行比较时,会比较错误。
我的猜测是编译器没有看到追加,就像它在b
上完成的那样,并且已经将比较优化为常量并且结果不正确。如果是这样,有哪些选项可以提示编译器。 a
能否以某种方式被标记为特殊?或者,除了与破坏性编程相关的样式问题之外,还有其他什么东西在这里发生吗?
(defun test-0 ()
(let ((a '(7))
b
(c '(7 1)))
(setq b a)
(setf (cdr b) (cons 1 '()))
(pprint (list a b c))
(values (equal c a) (equal '(7 1) a) (equal (list 7 1) a) c a)))
* (test-0)
((7 1) (7 1) (7 1))
T
NIL <== ??
T
(7 1)
(7 1)
这是在空载环境中加载和运行的脚本。该文件是上述代码的复制和粘贴。可以看到没有错误消息。有趣的是,这里看到的结果是不同的。
§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "src/test-0")
T
* (test-0)
((7 1) (7 1) (7 1))
NIL
NIL
T
(7 1)
(7 1)
*
答案 0 :(得分:5)
引用列表是&#34;文字&#34;,修改它们(就像在(setf (cdr b) ...)
中那样)会导致不明确的后果。来自test-0
的任何结果都是有效的,例如,格式化您的硬盘并炸掉您的房子。
将'(7)
替换为(list 7)
,您应该得到您期望的结果。
PS。请正确格式化您的代码。
答案 1 :(得分:4)
作为sds says,您正在修改文字数据,简短的回答是“不要这样做。”#34;修改文字数据是未定义的行为和&#34;任何&#34;可以发生。也就是说,结果通常是相对可预测的(例如,见Unexpected persistence of data),这种情况有点令人惊讶。似乎发生的事情是,当SBCL&#34;知道&#34;时,SBCL优化了呼叫(等于......)。这些值应该是不同的(因为它们是不同的文字)。
由于这是出乎意料的,我认为值得研究。我们可以将其分离为让 a 和 c 成为文字列表,然后修改 a 使其等于价值:
CL-USER> (let* ((a '(7))
(c '(7 1)))
(rplacd a '(1))
(equal a c))
; in: LET* ((A '(7)) (C '(7 1)))
; (RPLACD A '(1))
; --> LET PROGN SETF
; ==>
; (SB-KERNEL:%RPLACD #:N-X0 '(1))
;
; caught WARNING:
; Destructive function SB-KERNEL:%RPLACD called on constant data.
; See also:
; The ANSI Standard, Special Operator QUOTE
; The ANSI Standard, Section 3.2.2.3
;
; compilation unit finished
; caught 1 WARNING condition
NIL
现在让我们看一下表达式转换成的编译代码:
CL-USER> (disassemble (lambda ()
(let* ((a '(7))
(c '(7 1)))
(rplacd a '(1))
(equal a c))))
; disassembly for (LAMBDA ())
; Size: 60 bytes. Origin: #x10062874D4
; 4D4: 488D5C24F0 LEA RBX, [RSP-16] ; no-arg-parsing entry point
; 4D9: 4883EC18 SUB RSP, 24
; 4DD: 488B158CFFFFFF MOV RDX, [RIP-116] ; '(7)
; 4E4: 488B3D8DFFFFFF MOV RDI, [RIP-115] ; '(1)
; 4EB: 488B058EFFFFFF MOV RAX, [RIP-114] ; #<FDEFINITION for SB-KERNEL:%RPLACD>
; 4F2: B904000000 MOV ECX, 4
; 4F7: 48892B MOV [RBX], RBP
; 4FA: 488BEB MOV RBP, RBX
; 4FD: FF5009 CALL QWORD PTR [RAX+9]
; 500: BA17001020 MOV EDX, 537919511
; 505: 488BE5 MOV RSP, RBP
; 508: F8 CLC
; 509: 5D POP RBP
; 50A: C3 RET
; 50B: CC0A BREAK 10 ; error trap
; 50D: 02 BYTE #X02
; 50E: 19 BYTE #X19 ; INVALID-ARG-COUNT-ERROR
; 50F: 9A BYTE #X9A ; RCX
现在,您可以看到 rplacd 的功能调用位置,但您没有看到相等的功能。我想这里发生的是,当(7)和时,编译器知道相等将返回false 7 1)进行比较,并硬编码。
测试此方法的一种方法可能是参数化测试,以便编译器无法对其进行优化。果然:
(defun maybe (test)
(let* ((a '(7))
(c '(7 1)))
(rplacd a '(1))
(funcall test a c)))
通过这种方式,您可以获得预期的结果。现在 a 和 c 相等,但不是 eq :
CL-USER> (maybe 'equal)
T
CL-USER> (maybe 'eq)
NIL
测试此方法的另一种方法是拨打副本列表并将 a 的副本与 c进行比较(假设SBCL无法优化对复制列表的调用,以生成 a 原始值的副本):
CL-USER> (let* ((a '(7))
(c '(7 1)))
(rplacd a '(1))
(values (equal a c)
(equal (copy-list a) c)))
; ...
NIL ; (equal a c)
T ; (equal (copy-list a) c)