Fortran中INCLUDE和模块的区别

23
使用use语句引入模块和使用include语句引入独立文件有什么实际区别?我的意思是,如果我有一个在整个程序中经常使用的子例程:何时或为什么应该将其放在模块中,而不是只写在单独的文件中并在程序的其他部分中使用它?
此外,是否将所有打算放在模块中的子程序都编写在单独的文件中,并在模块内使用include会是一种好的做法?特别是当子程序中的代码很长时,可以使代码更加有组织(这样所有子程序都被打包到模块中,但如果我需要编辑其中一个,就不需要穿越大量代码)。
3个回答

31

这两种概念上的差异导致了非常明显的实际差异。

INCLUDE指令在源代码级别操作——它完成简单(“愚蠢”)的文本包含。如果在INCLUDE指令中没有任何特殊处理器对“文件名”进行解释(实际上并不需要是文件),那么程序员可以很容易地手动将完整的源代码拼接在一起,并将其与编译器一起使用,而源代码的语义没有任何区别。包含的源代码在隔离状态下没有真正的解释——其意义完全取决于引用所包含的源代码的上下文。

模块在程序的更高实体级别操作,即编译器实际考虑源码所描述的内容的级别。模块可以在其下游用户之外独立编译,一旦编译完成,编译器就知道模块能够为程序提供什么东西。

通常,使用INCLUDE指令的人想要做的是模块实际上设计目的。

示例问题:

  • 由于实体声明可以分布在多个语句中,因此包含源代码所描述的实体可能不是您所期望的。请考虑以下要包含的源代码:

    INTEGER :: i

    在隔离状态下,它看起来像是将名称i声明为整数标量(或者可能是一个函数?谁知道!)。现在考虑以下包括上述内容的作用域:

    INCLUDE "source from above"
    DIMENSION :: i(10,10)

    i现在是二维数组!也许您想将其设置为指针?可分配的?虚拟参数?也许这会导致错误,或者这是有效的源代码!加入隐式类型转换真的会使问题更复杂。

模块中定义的实体被模块“完全”定义。用途范围特定的属性可以更改(VOLATILE,可访问性等),但根本的实体保持不变。名称冲突明确指出,并且可以通过USE语句上的重命名子句轻松解决。

Fortran有关于语句排序的限制(规范语句必须在可执行语句之前等)。包含的源代码也受到这些限制的影响,再次提醒,是在包含点的上下文中,而不是源代码定义点。

对于某些完全晦涩的错误消息或更糟的是编译器对错误代码的静默接受,它与语句函数定义(规范部分)和赋值语句(可执行部分)之间的源代码模糊性混合在一起。

USE语句引用模块的位置有要求,但实际模块程序单元的源在其使用点上是完全独立的。

想要一些全局状态在相关过程之间共享并且您想要使用包含文件?那么让我介绍您认识一下公共块及其相关概念序列关联......

序列关联是早期基础Fortran处理器实现的不幸渗透,它是一个容易出错、不灵活、反优化的过时概念。模块变量使得公共块及其相关问题完全变得不必要。

如果您正在使用include语句,请注意您实际上没有包含常用程序的源代码(您第一段中的建议只会导致编译器产生大量语法错误)。您通常会包含描述该过程“接口”的源代码。对于任何非平凡过程,描述其接口的源与过程的完整源不同-这意味着您现在需要维护相同事物的两个源代码表示形式,这是一个易出错的维护负担。

如上所述-编译器自动获得了模块过程的接口信息(编译器知识是“显式”的,因为它实际上看到了过程的代码-因此术语“显式接口”)。程序员不需要做任何更多事情。

以上的结果是,除了存在循环或过于广泛的依赖关系(也许会有非常好的理由),外部子程序根本不应该被使用 - 基本起点应该将所有内容放入模块或主程序中。
其他帖子已经提到了模块的源代码组织优势-包括将相关过程和其他“东西”分组到一个包中,并控制内部实现细节的可访问性。
我认为问题的第二段提到的INCLUDE行的用法是有效的-当大型模块变得难以控制时。 F2008已经通过子模块解决了这个问题,还带来了许多其他好处。 一旦它们得到广泛支持,include行的解决方法就应该被放弃。
第二个有效的用途是克服语言对通用编程技术的支持不足(C++中提供的模板)-即,在一个操作中涉及的对象类型可能会有所不同,但描述在这些对象上要执行什么操作的标记序列本质上是相同的。也许还需要十年左右的时间,才能解决这个问题。

那么,将子程序分离到不同的文件中,然后在模块内使用include没有任何缺点,对吧?我以前从未听说过子模块。 - Nordico
1
只有当我有非常大的源文件或作为遗留源迁移的一部分时,才会考虑使用它。如果可能的话,我首先会考虑将模块分解为许多“子”模块,然后在父模块中使用USE语句进行聚合。但是,由于复杂的类型/过程依赖关系和/或Fortran的PUBLIC / PRIVATE可访问性工作方式,使用子模块并不总是可能的。您可能会发现,使用INCLUDE行将模块的源代码拼接在一起会使某些构建系统混淆。 - IanH

9
将过程放入模块中并使用这些模块可以使过程的接口明确化。这使得Fortran编译器可以检查调用中实际参数和过程的虚拟参数之间的一致性。这可以防止各种程序员错误。显式接口对于某些Fortran >=90的“高级”功能也是必要的,例如可选或关键字参数。如果没有显式接口,编译器将无法生成正确的调用。仅包含文件不能提供这些优势。

1
我更进一步认为,没有任何好的理由使用包含文件。 - High Performance Mark
1
就像我写的那样,“我不知道有什么好理由使用包含文件”。但这只是个人观点。 - High Performance Mark

4
M.S.B.的回答很好,可能是更喜欢模块而不是include的最重要原因。我想再补充几点。

如果编译后的二进制文件大小对您很重要,使用模块可以减小文件大小。模块只需编译一次,当您使用它时,实际上是符号性地加载该模块以使用代码。当您包含一个文件时,实际上是将新代码插入到您的程序中。如果您经常使用include,这可能会导致二进制文件变大并增加编译时间。

您还可以通过巧妙地使用模块中的公共和私有函数以及用户定义类型,在Fortran 90中模拟OOP样式的编码。即使您不想这样做,它也提供了一种很好的方式来组合逻辑上属于一起的函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接