This is a bug in sbcl?

2019-05-01 10:17发布

问题:

Why happen this in sbcl? Maybe a bug?

(defclass myclass ()
  ((s1
    :initform '((a . 1) (b . 2))) 
   (s2
    :initform '((a . 1) (b . 2)))))

(defparameter ins (make-instance 'myclass))

(setf (cdr (assoc 'a (slot-value ins 's1))) 43) ;; change only slot s1

;; here my problem

(slot-value ins 's1)  ;; => ((a . 44) (b . 2)))
(slot-value ins 's2)  ;; => ((a . 44) (b . 2)))

But if change :initform to :

(defclass myclass ()
  ((s1
    :initform '((a . 1) (b . 2))) 
   (s2
    :initform '((a . 1) (b . 3)))))

The problem disappears

I test this in sbcl 1.4.3 and 1.4.11. In clisp it seems that the problem does not arise.

回答1:

No. You are modifying literal data, which has undefined consequences.

When you quote a list in source code, it means that the thing you want to work with is exactly the list that the reader produced from your source code—this is a literal list. Such things may be remembered by the reader so that two identical lists are not duplicated.

One way to fix this is to create the lists at runtime, using list and cons:

(defclass myclass ()
  ((s1
    :initform (list (cons a 1) (cons b 2))) 
   (s2
    :initform (list (cons a 1) (cons b 2)))))


回答2:

This is not a bug. '((a . 1) (b . 2)) is a literal constant and as all constants assumed immutable. That means all occurrences of '(a . 1) that also are literal can just point to the car of the other one since it should never change

Now implementations can choose to make new structures so CLISP might do that, but you cannot rely on this. You should not mutate literal data.

If you are going to change it you need to use a deep copy, like this:

(defclass myclass ()
  ((s1
    :initform (copy-tree '((a . 1) (b . 2)))) 
   (s2
    :initform (copy-tree '((a . 1) (b . 2))))))