Compilation 一个普通的Lisp编译器需要重新编译多少次?

Compilation 一个普通的Lisp编译器需要重新编译多少次?,compilation,common-lisp,Compilation,Common Lisp,虽然并非所有常见的Lisp实现都会编译机器代码,但有些实现会编译机器代码,包括SBCL和CCL 在C/C++中,如果源文件不变,C/C++编译器的二进制输出也不会改变,前提是底层系统保持不变 在普通的Lisp编译器中,编译不受用户的直接控制,这与C/C++不同。我的问题是,如果Lisp源文件没有更改,在什么情况下CL编译器会多次编译代码,为什么?如果可能的话,一个简单的例子会很有帮助。我认为这个问题是基于一些误解。编译器不编译文件,用户也不能控制它。通过函数可以很容易地使用编译器。编译器对代码而

虽然并非所有常见的Lisp实现都会编译机器代码,但有些实现会编译机器代码,包括SBCL和CCL

在C/C++中,如果源文件不变,C/C++编译器的二进制输出也不会改变,前提是底层系统保持不变


在普通的Lisp编译器中,编译不受用户的直接控制,这与C/C++不同。我的问题是,如果Lisp源文件没有更改,在什么情况下CL编译器会多次编译代码,为什么?如果可能的话,一个简单的例子会很有帮助。

我认为这个问题是基于一些误解。编译器不编译文件,用户也不能控制它。通过函数可以很容易地使用编译器。编译器对代码而不是文件进行操作。例如,您可以在REPL处键入

CL-USER> (compile nil (list 'lambda (list 'x) (list '+ 'x 'x)))
#<FUNCTION (LAMBDA (X)) {100460E24B}>
NIL
NIL
CL-USER>(编译nil(list'lambda(list'x)(list'+'x'x)))
#
无
无
根本不涉及任何文件。但是,也有一个函数,但请注意,其描述如下:

编译文件转换由指定的文件的内容 将文件输入到与实现相关的二进制数据中 在输出文件指定的文件中

文件的内容将被编译。然后该编译文件可以被重新编译(您也可以加载未编译的源文件)。我认为您的问题可以归结为询问在什么情况下编译文件会生成具有不同内容的文件。我认为这实际上取决于实现,而且不可预测。我不知道您对其他语言的编译器的描述必然包含以下内容:

在C/C++中,如果源文件不变,则 C/C++编译器也不会改变,假定底层系统 保持不变


如果编译器碰巧在某些数据段的输出中包含时间戳,该怎么办?然后每次都会得到不同的二进制输出。的确,一些常见的脚本编译/构建系统(例如,make和类似系统)会根据输入文件是否同时发生更改来检查以前的输出是否可以重用。不过,这并没有真正说明编译器的功能。

规则基本相同,但在常见的Lisp中,将声明与实现分开并不是一种做法,因此通常必须重新编译每个依赖项才能确保。这是动态环境的共同实践结果


假设存在这样的分离,以下是需要重新编译特定依赖文件的更改的平淡示例(显然不是详尽的),因为输出可能不同:

  • 更改的包定义
  • 更改的宏字符或其代码中的更改
  • 已更改的宏
  • 添加或删除
    inline
    notinline
    声明
  • 全局类型或函数类型声明中的更改
  • #
    defvar
    defparameter
    defconstant
    load time value
    eql
    specializer、
    生成加载表单
    生成的代码、
    defmacro
    等(如setf扩展器)中使用的函数发生了变化
  • Lisp编译器或基本映像中的更改
我的意思是,您可以看到,确定哪些文件需要重新编译并不是件小事。有时,答案是“所有后续文件”,例如更改
(双引号)宏字符,这可能会影响每个文本字符串,或者编译器以不向后兼容的方式发展。本质上,我们从开始的地方就结束了:您只能确保完全重新编译,而不能跨编译重用FASL。有时,这比确定需要重新编译的最小文件集要快

实际上,在开发过程中(例如使用Slime)会大量编译单个定义,并且当存在比源文件旧或旧的fasl时,不会重新编译文件。很多时候,您会重用来自例如Quicklisp的文件。但是对于测试和部署,我建议清除所有fasl并重新编译所有文件

人们一直在努力用SBCL自动化最小依赖项编译,但我认为,当您更频繁地更改临时项目时,这太慢了(它涉及很多分叉,因此在Windows中,它要么不可行,要么非常慢)。然而,对于很少更改的基本库来说,这可能是一个节省时间的方法,如果有的话


另一种方法是使用内置的基本库(即始终加载的库)生成自定义的基本映像。这将节省编译和加载时间。

编译器不编译文件。它编译代码。例如,您可以在REPL处键入
(编译nil(list'lambda(list'x)(list'+'x'x)))
。根本不涉及任何文件。@JoshuaTaylor尽管如此,仍然可以将代码放入文件中,并从文件中编译。当然(请参阅我的答案),但在这种情况下,一些代码是从一个文件中读取的,编译后的内容被写入另一个文件,然后可以加载。@JoshuaTaylor好的,如果这个问题足够不适定,也许删除它会是一个好主意。你的意见是什么?我不一定认为这是一个坏问题;有错误的假设是一件好事这是我们过去都做过的,很多人对Common Lisp中的编译工作有一些误解。我认为这个问题及其答案可能对未来的人们有用。实际上,文件编译器编译文件。COMPILE只编译单个函数。请参阅文件编译器的CLHS文档。是的,有一个文件编译器我特别提到编译文件是为了指出,是的,我们通常会“编译文件”,实际上,编译的不是文件