Answers:
不同的语言(以及编译器)对此的处理方式也有所不同。
在C系列中,不同的模块具有在构建对象时使用的对应头文件。头文件提供有关对象大小以及可以调用的功能或方法的信息。这为内存分配提供了必要的信息,并且“该方法/功能/程序是否存在?” 在编译不需要访问源本身的单个单元时使用。
在Java中,编译器知道其类路径上的内容,并检查这些对象以链接到它们(验证方法是否存在,参数数量正确等)。Java也可能在运行时加载时动态链接到其他类,这些类对编译时一无所知。有关动态加载的示例,请参见Class.forName。
两种选择都非常有效,并且各有优缺点。提供头文件有些麻烦且违反DRY。另一方面,如果您没有头文件,则编译器和链接器都需要检查库文件-.so或.dll中可能没有足够的信息来正确实例化对象或验证方法调用(并取决于机器)。
实际上,使用Java,IDE可以一次查看整个程序。引用ClassB时,IDE编译器将对其进行查看。包括库在内的所有内容都是一个完整的整体。程序准备就绪后,您可以更改类路径,换入和换出单个.class文件,以及切换库版本。您还可以在不使用IDE的情况下编译单个.java文件(或以某种方式避免对其进行检查)。结果根本不需要保持一致,如果不一致,您将获得运行时异常。(IDE试图为您做的许多事情之一就是将运行时错误转换为编译时错误,或更确切地说是编辑时错误。)
C#基本相同,我认为C和C ++并没有真正的不同,因为Java和C#IDE所做的只是在幕后为您创建C / C ++样式的标头。
较旧的语言有时更严格;考虑一下Java中可能发生的情况:
public interface Ifc {
public static final Ifc MY_CONSTANT = new Implem();
}
public class Implem implements Ifc {
}
我已经看到了上面的反模式,而且确实很丑陋(我本来会禁止的)。两个编译单元互相使用。但是Ifc可以编译为代码而无需编译的Implem。类似于C .obj的已编译代码.class包含“链接信息:” Implem的导入,调用无参数构造函数Implem()
。然后可以毫无问题地编译Implem类。部分是ClassLoader-执行初始化/构建JVM类数据,部分是Java虚拟机本身,充当链接器,集成所有内容。
例如,使用特定库的一个版本进行编译,然后使用该库的另一个版本运行,将识别运行时错误。
答案就是:编译提供了已编译目标代码的单元,必须将它们视为代码+数据+用于链接在一起的API。
此后,编译器还应一起打包,并验证链接API;第二阶段。
这可能会令人烦恼并且看起来不雅,但是数学证明可能以相同的方式起作用:在证明整个正确性之前,人们可能已经考虑过一个部分,直到验证。