虽然并非所有Common Lisp实现都能编译到机器代码,但其中一些实现了,包括SBCL和CCL。
在C / C ++中,如果源文件没有改变,假设底层系统保持不变,C / C ++编译器的二进制输出也不会改变。
在Common Lisp编译器中,与C / C ++不同,编译不在用户的直接控制之下。我的问题是,如果Lisp源文件没有改变,在什么情况下CL编译器会不止一次编译代码,为什么?如果可能的话,一个简单的说明性示例会有所帮助。
答案 0 :(得分:1)
我认为这个问题是基于一些误解。编译器不编译文件,并且它不是用户无法控制的东西。编译器很容易通过compile函数获得。编译器在代码上运行,而不是在文件上运行。例如,您可以在REPL中输入
CL-USER> (compile nil (list 'lambda (list 'x) (list '+ 'x 'x)))
#<FUNCTION (LAMBDA (X)) {100460E24B}>
NIL
NIL
根本没有文件涉及。但是,还有compile-file函数,但请注意其描述为:
compile-file转换由指定的文件的内容 输入文件到放置的依赖于实现的二进制数据 在output-file指定的文件中。
编译文件的内容。然后编译文件可以load编辑。 (您也可以加载未编译的源文件。)我认为您的问题可能归结为询问编译文件在什么情况下生成具有不同内容的文件。我认为这确实依赖于实现,而且它并不是真正可预测的。我不知道你对其他语言的编译器的描述必然要么:
在C / C ++中,如果源文件没有改变,则a的二进制输出 假设底层系统,C / C ++编译器也不会改变 保持不变。
如果编译器恰好在某些数据段的输出中包含时间戳,该怎么办?然后你每次都会得到不同的二进制输出。确实,一些常见的脚本编译/构建系统(例如, make 和类似的)将检查是否可以根据输入文件在此期间是否已更改来重复使用先前的输出。但是,它并没有真正说出编译器的功能。
答案 1 :(得分:1)
规则几乎相同,但在Common Lisp中,将声明与实现分开并不是一种惯例,因此通常必须重新编译每个依赖项才能确定。这是动态环境的共同实际结果。
想象有这样的分离,以下是需要重新编译特定依赖文件的更改的明显例子(显然不是详尽无遗),因为输出可能不同:
inline
或notinline
声明#.
,defvar
,defparameter
,defconstant
,load-time-value
,eql
专精,make-load-form
中使用的已更改功能生成的代码,defmacro
等(例如setf expandders)... 我的意思是,您可以看到确定哪些文件需要重新编译并不容易。有时,答案是&#34;所有后续文件&#34;,例如更改可能影响每个文字字符串的"
(双引号)宏字符,或者以非向后兼容的方式演变的编译器。本质上,我们结束了我们开始的地方:你只能确保完全重新编译,而不是在编译中重复使用fasls。有时,它比确定需要重新编译的最小文件集更快。
在实践中,您最终会在开发过程中大量编译单个定义(例如使用Slime),而不会重新编译文件,因为这些文件比源文件更旧或更年轻。很多时候,您重复使用例如Quicklisp。但是对于测试和部署,我建议清除所有的fasls并重新编译所有内容。
已经努力使用SBCL自动化最小依赖项编译,但我认为当你更频繁地更改临时项目时它太慢(它涉及很多分叉,所以在Windows中它&#39;无论是不可行还是非常慢。但是,如果有的话,基本库可能会节省很多时间。
另一种方法是使用内置的基本库(即您始终加载的基本库)制作自定义基本映像。它将保存编译和加载时间。