2016-01-19 1 views
2

SBCL 1.3.1# 'равное сравнение true по сравнению с (список 7 1), но false по сравнению с' (7 1), почему?

В общем, a является список, '(7), b устанавливается в тот же список с помощью setq. Значение добавляется к b. Список c установлен на ожидаемый результат после добавления, то есть '(7 1). a затем сравнивается с c и правильно сравнивает значение true. Однако, когда a сравнивается через (equal a '(7 1)), он сравнивает значение false.

Я предполагаю, что компилятор не видел приложение, как это было сделано на 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) 
* 
+1

Вы должны избегать использования цитируемых списков при привязке значений к переменной. Они всегда склонны вызывать странное поведение при изменении (просто замените их на '(list ...)', и код должен работать). – jkiiski

+0

Да, это исправляет его, и имеет смысл, что цитируемые списки будут постоянными и недоступны для модификации. Если вы хотите, чтобы это как «ответ», добавьте его, и я отметю его. –

+0

@ user244488 Это подходящий ответ для «что мне делать, чтобы этого избежать», но это не объясняет поведение, которое вы получаете. Модификация * does * происходит, фактически (хотя это неопределенное поведение); поведение заключается в том, что тест на равенство оптимизируется. Я добавил ответ с некоторыми подробностями. –

ответ

4

Как sds says, вы изменяете литеральные данные, а короткий ответ - «не делайте этого». Изменение буквенных данных - это неопределенное поведение, и может произойти что угодно. При этом обычно результаты относительно предсказуемы (например, см. Unexpected persistence of data), и этот случай немного удивителен. Похоже, что происходит то, что SBCL оптимизирует вызов (equal & hellip;), когда он «знает», что значения должны быть разными (поскольку они разные литералы).

Поскольку это своего рода неожиданное, я думаю, что стоит посмотреть. Мы можем выделить это вниз, чтобы позволить в и гр быть буквенные списки, а затем изменить в, чтобы сделать его значение равно:

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, но вы этого не сделаете себе e one для . Я думаю, , что здесь происходит, что компилятор знает, что равно вернет ложь, когда (7) и (7 1) сравниваются, и жесткие коды он.

Одним из способов проверки этого может быть параметризация теста, чтобы компилятор не мог его оптимизировать. Уверен:

(defun maybe (test) 
    (let* ((a '(7)) 
     (c '(7 1))) 
    (rplacd a '(1)) 
    (funcall test a c))) 

С помощью этого вы получите результаты, которые, возможно, ожидали. Теперь и с являются равным, но не эк:

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) 
+0

Джош, спасибо, что действительно связывает это вместе. Эта ошибка была бы полезной, но, по-видимому, компилятор сделал некоторую оптимизацию, чтобы этого не произошло. Это интересно. –

+0

Какая ошибка была бы полезной? Когда вы скомпилировали свой код, вы должны получить аналогичное диагностическое сообщение. Когда я запускаю свой код, я получаю подобное сообщение. –

+0

Кстати, спасибо за пример кода сборки. Прошло некоторое время с тех пор, как я прочитал много кода x86, но я последовал за этим - во многом из-за ваших комментариев, поскольку я понятия не имел о адресе для rplacd (или, возможно, вы предполагаете это из заказа). Тест, который я процитировал в исходном посте, test-0, компилируется без какого-либо шума. –

5

Цитируемых списки «литералы», изменяя их (как вы в (setf (cdr b) ...)) приводят к неопределенным последствиям. Любой результат от test-0 будет действительным, например, форматирование жесткого диска и взорвание вашего дома.

Замените '(7) на (list 7), и вы должны получить то, что ожидаете.

PS. Пожалуйста, правильно отформатируйте свой код.

+5

Я просто должен сказать, как это было на самом деле, когда вы отправили это, сосед подошел и сказал, что она пахнет чем-то горящим и обеспокоена. –

Смежные вопросы