Common Lisp 类型注释会导致不正常的行为吗?

Can Common Lisp type annotations result in unsound behavior?

我知道如果安全设置低,Common Lisp 可以使用类型注释作为优化辅助,并且不会被检查。例如,此程序运行并打印数字和字符串,没有任何类型错误。 (当 safety >= 1 时,我只在 SBCL 中遇到类型错误)

(declaim (optimize
           (speed 3)
           (safety 0)))

(defun f (x)
  (declare (type fixnum x))
  x)

(format t "1 ~A~%" (f 17))
(format t "2 ~A~%" (f "asd"))

我现在想知道如果安全设置为零并且不遵守类型注释,是否有可能创建一个程序来做一些令人讨厌的事情。诸如从一种类型转换为另一种类型(如 C 类型转换)和其他类型的未定义行为。

到目前为止,我还没有找到这样做的例子。我尝试了使用类型化数组的此示例的变体,但其中 none 导致了类型转换行为。

(declaim (optimize
           (speed 3)
           (safety 0)
           ))

(defun f ()
    (let ((arr (make-array '(5)
      :element-type 'fixnum
      :initial-contents (list 1 2 3 4 5))))
      (declare (type (vector fixnum 5) arr))
      (setf (aref arr 0) "hello")
      (aref arr 0)))

(format t "a1 ~A~%" (f))

在 CLISP 中,此程序打印 "hello",但未对 int 进行类型转换,而在 SBCL 中,程序因 SIMPLE-TYPE-ERROR 错误而中止。

有没有办法创建一个 Common Lisp 程序,如果我不遵守我的类型声明,它就会让恶魔从我的鼻子里冒出来?

不确定这些恶魔,但是你可以遇到段错误,就像我 15 多年前使用 CMUCL 时所做的那样以及类似于此代码的东西:

(declaim (optimize (speed 3) (safety 0)))
(defun f (v)
  (declare (type (vector double-float) v))
  (loop for x in v sum x))
(f #(1 2 3))

请注意,我承诺我只会将 (vector double-float) 传递给 f,然后我给它一个 simple-vectorfixnum

Common Lisp 标准说有类型声明。它并没有真正说明它们做什么或实现将对它们做什么。

用途:

  • 生成类型特定代码
  • 运行-time and/or 编译时类型检查。这主要是由 CMUCL、SBCL 和 SCL 完成的。

假设我们有操作:

(+ i 100)

默认情况下,它将是一个泛型 +,它可以处理所有数字类型。

如果我们告诉 i 是一个 fixnum 那么

  • + 操作可以特定于 fixnum

如果另外将 return 类型声明为 fixnum:

  • 它可能不会溢出到一个 bignum

如果我们额外告诉编译器我们想要低 safety,那么编译器将不会生成 运行time 类型检查。

编译器提供的功能没有标准化。它甚至可以完全忽略类型声明。

如果你有一个支持特定类型代码的编译器(并且有很多这样的编译器),安全性很低并且在 运行 时提供了错误类型的对象,那么它可能会产生意想不到的后果.包括由于堆内存损坏导致 Lisp 崩溃。

因此,最好只在非常小的代码区域而不是整个文件或系统上使用低安全性。使用带有 locally 的声明会有所帮助。

SBCL(也称为 SCL 和 CMUCL)比较特殊,因为它也将类型声明视为类型检查的断言。

以下是 sbcl 中 safety 0 的几个示例:

CL-USER> (proclaim '(optimize (safety 0)))
; No value

读取越界

CL-USER> (loop for i from 0 to 20 
              do (print (aref #(0) i)))

0 
0 
#(0) 
(I) 
I 
NIL 
(AREF #(0) I) 
NIL 
(PRINT (AREF #(0) I)) 
NIL 
#<unknown immediate object, lowtag=#b1001, widetag=#x59 {D59}> 
#<SB-KERNEL:LAYOUT for SB-KERNEL:LEXENV {10005F3C33}> 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL 
NIL

未定义类型转换

CL-USER> (defun f (x y)
           (declare (fixnum x y))
           (+ x y))
F
CL-USER> (f t ())
537919538

在 SBCL 中,如果你闻到鼻恶魔的味道,你可以在加载有问题的代码之前重新启动图像并输入 (sb-ext:restrict-compiler-policy 'safety 3)(也可能再次输入 'debug)。运气好的话,你会得到一个不错的条件,而不是未定义的行为。