2

我继承了大约 400 行写得很奇怪的 Fortran 77 代码,我试图一步一步地分析它,以便在我的脑海中清晰。

无论如何,我有一个类似标题的文件(实际上是一个.h,但其中的代码是 fortran 而不是 C/C++),其中只有两个语句,称为getarg.h

character*80 serie
integer ln

然后我有另一个.f名为的 fortran 文件 () getserie.h,其中包含以下代码:

subroutine getserie(serie, ln)
include 'getarg.h'
call getarg(1, serie)
ln = index(serie, ' ') - 1
return
end

我的问题是:我可以call创建一个仅包含变量声明的外部文件吗?这样做有什么效果?

4

3 回答 3

6

不,您只能调用子例程。这意味着指定为 的子程序subroutine。但是,子例程的定义不必在源文件中。它只需要在链接时提供。

getarg子例程可能是您的编译器的一个内在子例程,它获取命令行参数。这意味着编译器会自动将子程序的代码提供给链接器。

该文件getarg.h不会以任何方式调用。它的内容只是直接复制到include语句所在的地方。

在某些情况下,您需要提供被调用子例程的(显式)接口,但在更高版本的 Fortran 90 和更高版本中。在这些现代版本中,您通常将子例程和函数放在模块中,以便编译器可以检查您是否正确调用它们。

于 2012-11-01T14:43:23.693 回答
1

大多数 FORTRAN实现的一个特殊特性是CALL可以调用几乎任何外部符号。当然,只调用SUBROUTINEs 是有道理的。这是可能的,因为众所周知的 FORTRAN 调用约定——所有实际参数都通过地址传递,即所有实际传递给子程序的参数都是相同类型的——它们都是指针。甚至常量也是通过地址传递的 - 包含常量值的内存位置的地址。这允许编译器生成对任何子例程的调用,而无需提供显式原型。它只是将所有参数指针压入堆栈(在具有基于堆栈的调用约定的平台上)或将它们加载到相应的寄存器中(在具有寄存器调用约定的平台上)并向符号的地址发出汇编call指令,用作子程序中的名字CALL陈述。如果这样的符号在链接时存在,链接器将生成一个可执行文件,否则它将抱怨未解析的符号引用并且不会生成任何可执行文件。

函数调用也是如此。编译器用来从数组索引操作告诉函数调用的唯一特性是(非内在)函数被声明为EXTERNAL. 特殊EXTERNAL指定是必要的,因为调用函数和访问数组元素的语法是相同的。还有一些特殊的预定义函数,称为内在函数,编译器可以在内部识别它们(例如SIN)。也有内在的子程序,但大多数只是库子程序。

因此,每当您遇到一个CALL foo(...)语句并且您不知道它是什么foo时,您应该查阅您的编译器手册以查看是否存在同名的内在函数。如果没有,它可能是在代码中的某个地方定义的 - 查找SUBROUTINE foo. 如果不是,它应该是从外部库导出的子例程,链接到程序中,或者只是语法错误。

这一切都非常容易出错,因为可以很容易地提供与子例程期望的完全不同类型的实际参数,或者甚至可以提供不同数量的实际参数,编译器仍然会愉快地编译代码。当然,这样的程序在运行时通常会崩溃或产生无意义的结果。这就是为什么要flint开发专门的程序,以便从整体上检查源代码中编译器无法检测到的问题(例如参数类型不匹配、实际参数数量错误等)。Fortran 90 通过引入接口解决了这个问题。接口在某些情况下是强制性的,但通常可用于提供编译时参数检查。

于 2012-11-01T18:24:54.700 回答
0

如前所述,这里唯一奇怪的是包含文件名称的可疑选择。(.h 扩展名和内在符号名称的不相关使用)。(是的,我知道 getarg 不是迂腐标准的 f77,而是一个扩展)

将变量声明放在包含文件中是(/was)常见的做法,以确保程序单元之间的声明一致。毫无疑问,该文件也“包含”在调用程序中,并确保您的字符串在任何地方都以相同的长度声明。

这就是说在这个例子中它肯定是没有必要的。您可以将字符串声明为假定长度,因此请代替包含:

character serie*(*)
integer ln

以及主程序中的正常声明,并摆脱包含文件的混乱。

(现在有人会告诉我上世纪初的一些编译器不支持假定长度的字符串声明)..

于 2012-11-03T13:37:35.093 回答