Fortran中模块的正确使用方法

6

我经常使用FORTRAN,但从未接受过正式的源代码编写指导。我目前使用模块来存储全局变量,但我知道你也可以使用它们来存储子程序和函数。我处理的代码有许多子程序,因为它们非常庞大和复杂。所有函数和子程序都应该在模块中吗?如果是,为什么?


非常好的讨论,很有帮助。在构建我的程序时,我会记住这些事情。 - rks171
1
虽然你的问题比较广泛,但它为下面精彩的回答提供了很好的催化剂。点赞! - JonnyB
4个回答

22

一般来说,对于您的第一个问题,答案是,关于第二个问题,我马上会给出答案。请注意,这是对于一般问题的一般回答,而那些精通SO Fortran问题的人可能会提出特殊情况,在这些情况下模块是不适用的。我预先反驳一下,这个答案是针对新手学习模块使用的。一旦您不再是新手,您可以自己回答您的问题。

对于程序员而言,模块作为组织和结构化程序或一组程序的工具非常有用。它们提供了一种机制,用于封装用户定义类型的定义以及操作这些类型的函数/子程序的定义。在Fortran 90和95中,这种封装有点临时性,因为它依赖于程序员如何将程序分解成部分的想法。随着Fortran 2003中面向对象设施的引入,现在有更清晰的“规则”来确定哪些元素属于每个模块。

例如,您可以构思一个用于有理算术的类型和过程的模块。通过将实现您伟大想法的所有代码保存在一个模块中,您可以将其从程序的其他部分(不需要知道细节的部分)隐藏,并仅公开您希望公开的那些部分(请查看PRIVATEPUBLIC关键字)。你可以立即看到将代码组织成模块的另一个好处;在新程序中使用您的有理算术模块比将代码从您的大型源文件中剪切粘贴到另一个大型源文件中要容易得多。当您想要处理您的有理算术时,您只需处理一个模块中的代码,而不是处理分散在文件周围的代码。

模块也允许您管理名称冲突。例如,您的有理算术模块可能会定义一个名为add的操作,而您还可能有一个定义了名为add的操作的多精度整数算术模块。如果您尝试在程序(或另一个模块)中使用这两个模块,则编译器将警告(可能引发错误),因为在使用模块的范围内定义了相同的名称两次。当使用关联模块实体时,可以使用重命名。您也可以使用ONLY子句仅导入用户需要的那些模块实体。

请注意,模块USE是传递的,如果A使用B且B使用C,则无需声明A也使用C(尽管如果您已重命名实体或指定了ONLY子句,则必须确保在特定情况下它是传递的)。

简而言之,通过将程序分解成可管理的块,模块是处理程序复杂性的主要Fortran机制。当编译器实现该功能时,Fortran 2008引入了SUBMODULE,这些子模块承诺以更好的方式支持处理复杂性。

模块也很有用,因为语言标准要求编译器为在模块中定义的过程生成显式接口,以便在编译时进行参数类型检查。请注意,这些接口(您实际上从未看到)称为显式接口,以与没有在模块内定义(或CONTAIN在使用它们的程序单元内)的过程的隐式接口形成对比。当然,您可以为这些过程编写显式接口,但短期和长期来看,让编译器为您完成通常更容易。

正如@Telgin已经指出的那样,模块还有助于递增编译。


1
我不明白模块如何有助于增量编译。实际上,(就我而言)编译困难是不使用模块的最好论据。(我并不主张我们摆脱模块只是因为它们更难编译 - 我只是不明白它们到底有什么帮助)。 - mgilson
通常情况下,如果我已经编译了一个模块,并且在链接时已经存在了.o.mod文件,并且它们的时间戳正确,那么我的Makefile就不会重新编译它们。不过,我知道当修改模块但其接口保持不变时,会发生Fortran编译级联 - High Performance Mark
1
但是如果你已经有了一个具有正确时间戳的已编译的.o文件,那也是可以的(与模块无关)。模块的问题在于它们强制执行一种编译顺序,而否则是不存在的,并且编译一个模块会生成2个文件,这是一个(可悲的)make似乎完全忘记的情况。但我认为,通过使用模块获得的功能和安全性所付出的代价很小。 - mgilson
1
啊,我明白你的意思了。我不能说编译顺序是个问题,Fortran 在其他情况下也需要这样做(例如在模块中定义类型之前不使用它)。但也许是因为我已经和 Fortran 工作太久了。我很清楚现代软件工程师认为的许多“问题”实际上对我来说只是景观的一部分。 - High Performance Mark

6
使用模块的主要好处之一是,编译器会自动对从模块中use的任何函数或子程序执行接口检查,以确保您使用适当的参数类型调用例程。关于这个主题的一个很好的文章是Doctor Fortran Gets Explicit - Again!。来自这篇文章的内容如下:

提供显式接口的几种方法。最简单和最好的方法是将过程放入模块中,或者将其作为调用程序或过程的CONTAINed过程。这样做的好处是不需要在两个地方写信息,从而增加出现错误的机会。当您拥有模块过程或包含过程时,它的接口会自动对模块或父级范围中的其他所有内容可见。假设该名称未声明为PRIVATE,则该接口也可用于使用包含模块过程的模块的地方。

我建议将所有相关例程放在同一个模块中。对我来说,模块相当于其他语言中的类。模块是一种将相关数据和例程(可能操作该数据)分组的方法。因此,如果您的例程在逻辑上分组到模块中,则模块提供了一种使代码更易于导航的方法,并为函数和子例程调用添加类型检查。

3

2
将相关的函数、子程序和变量移至它们自己的模块中可以提高程序的可维护性。在以后需要更新其中一部分时,您不必深入挖掘一个包含数万(或数百万)行代码的单个源文件,只需打开相关文件即可。
当然,您可能认为可以使用编辑器的搜索功能来查找相关的子程序,但是几个月后让代码独自存在后,您可能很快发现无法完全回忆起子程序的名称或它们如何组合在一起。将它们分组有助于解决这个问题。
将子程序和函数移至模块中还可以提高编译速度,如果编译器只需要重新构建单个模块而不是整个程序。

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