Linux 下的 Fortran 编程工具
尽管对 Linux 支持穿孔卡片阅读器的需求不大,但我们中仍然有相当多的人在编写计算机代码的日子里学会了编程,那时编程能力是通过您在 “360” 队列中的卡片组大小来判断的。当然,那时,Fortran 是工程师和科学家首选的the高级语言,并且是包括我在内的许多人学过的唯一编程语言。
许多人(好吧,也许大多数人)认为与现代计算机语言相比,Fortran 已经过时了;但是受过 Fortran 训练的工程师、研究人员和科学家对这些论点置之不理。我们认为编程只是一种实现目标的工具,我们最熟悉的工具效率最高;因此,我们坚持使用 Fortran。对我们来说幸运的是,Linux 提供了丰富的 Fortran 编程工具选择。
本文调查了 Linux 中为从非 Unix 环境(例如 VAX/VMS 或 DOS)迁移到 Linux 的 Fortran 用户提供的一些基本工具。我的目标是说服 “Fortran 老顽固” 同行,他们可以投入 Linux 的怀抱,而不会牺牲他们的 “原生” 编程能力。(值得注意的是,在最近的Linux Journal调查中,Linux 用户将 Fortran 在 25 种编程语言中排名第五。)
在 Linux 下编译 FORTRAN 77 程序有几种选择。最成熟的方法是由 AT&T Bell 实验室和 Bellcore 开发的公共领域 Fortran 到 C 转换器。这个 Fortran 转换器,被称为 f2c,通常包含在所有流行的 Linux 发行版中。第二个选择是新发布的 GNU Fortran 编译器,称为 g77 (参见侧边栏)。由于 GNU g77 仍在进行 beta 测试,并且不像 f2c 那样广泛分发,因此本文仅限于使用 f2c。最后,至少有两个商业 Fortran 编译器可用于 Linux:NAG 的 F90 Fortran 90 编译器和 Microway 的 NDP Fortran 编译器。
f2c 转换器读取 FORTRAN 77 源代码文件,并将其转换为等效的 C 语言源代码。这听起来像是不可能完成的任务,但 f2c 开发人员已经完成了出色的实现。第二步涉及使用 GNU C 编译器 gcc 编译生成的 C 代码。此步骤包括自动创建可执行文件,并正确链接到必要的静态和共享库。
像往常一样,Unix 的新用户面临着与 f2c 和 gcc 相关的令人眼花缭乱的选项。幸运的是,我们受到名为 f77 的 bash shell 脚本的保护,免受了 Unix 的 “原始” 力量的影响。此脚本的选项较少,并且通过充当 f2c/gcc 组合的接口,使编译 Fortran 程序变得容易。在我的 Slackware 发行版中,f77 脚本位于 /usr/bin 目录中。我没有 f77 的 man 页面,尽管它可能存在。幸运的是,您开始使用 f77 所需的所有信息都列在 f77 脚本本身的前 18 行中。
下面列出的简单程序说明了在 Linux 下编译 Fortran 程序的基础知识。(我知道子例程不是真的必要,但我包含它只是为了说明本文后面的某些要点。)
C============================== C Simple Program to Illustrate C Fortran Programming Tools C============================== PROGRAM F77DEMO DIMENSION X(100), Y(100) PI=2.*ACOS(0.) N=100 DO 10 I=1,N X(I)=I*(2*PI/N) 10 CONTINUE CALL TRIG(N,X,Y) DO 20 J=1,5 PRINT 15, X(J), Y(J) 15 FORMAT(2X,2F8.3) 20 CONTINUE STOP END C SUBROUTINE TRIG(N,X,Y) DIMENSION X(1), Y(1) DO 10 I=1,N Y(I) = SIN(X(I))*EXP(-X(I)) 10 CONTINUE RETURN END
编译和链接我们的示例程序,名为 f77demo.f,很容易(在本文中,您键入的命令前面带有 shell 提示符 $.)
$ f77 -o demoexe f77demo.f f2ctmp_f77demo.f: MAIN f77demo: trig:
没有列出任何错误,因此我们知道 MAIN 程序和子例程 trig 编译成功。可执行文件 demoexe 已准备好运行,只需在命令行上键入 demoexe 即可。(如果 demoexe 不在您的 $PATH 中,您将必须指示它的位置——在本例中,通过键入 ./demoexe。)在编译过程中,f77 脚本还创建了一个名为 f77demo.o 的目标模块。
f77 脚本期望 Fortran 源代码位于扩展名为 .f 的文件中。如果您正在移植具有不同文件扩展名的代码,例如 .FOR 或 .FTN,请将其更改为 .f。(移植 DOS 文件时,请使用 fromdos 程序删除行尾 Control-M 和文件尾 Control-Z 字符。如果您没有 fromdos 程序,请尝试使用 清单 1 中的 shell 脚本。)-o 开关告诉脚本创建一个名为 demoexe 的可执行文件(在本例中)。如果没有此开关,可执行文件名默认为 a.out。
可以使用 f77 将多个源文件编译到可执行模块中。例如,如果我们把我们的示例程序分成两个文件,分别名为 maindemo.f 和 trig.f,我们的命令行将是
$ f77 -o demoexe maindemo.f trig.f
对于仅包含少量子例程的程序,这种用法可能是典型的。对于大型程序开发,可以使用 make 实用程序来提高效率。[参见Linux Journal 第 6 期或阅读 GNU make 手册。—编者] f77 脚本还允许您同时编译 Fortran、C 和汇编源文件,方法是在命令行中包含源文件名。此活动将留给读者作为练习...
在几个 f77 命令行选项中,有两个特别有用。第一个是 -c 选项。此选项告诉 f77 创建一个目标模块,而不是可执行文件。例如,命令
$ f77 -c trig.f
将在您当前的工作目录中创建目标文件 trig.o。我们为什么要这样做?一个原因是使用 Linux 提供的库管理器创建我们自己的 Fortran 可调用子例程的自定义对象库。另一个原因是创建 Fortran 子例程和其他 Linux 程序之间的动态链接(稍后详述!)。
第二个重要的 f77 开关是 -llib 选项。此选项用于告诉 gcc 编译器在用户提供的库中搜索子例程调用,这些库通常不由 gcc 检查。静态对象库的命名约定为 libmystuff.a,其中 -l 选项中使用的名称是夹在 lib 和 .a 之间的部分。共享库也以 lib 开头,但具有类似于 .so.#.##.# 的扩展名,其中 #-符号被数字替换,这些数字与库版本相对应。
命令说明了在编译 Fortran 程序时包含库
$ f77 -o foobar foobar.f -lmystuff -lmorestuff
在编译期间,gcc 编译器将搜索 libmystuff.a 和 libmorestuff.a 库(按此顺序),以查找任何未解析的子例程和函数调用。库的顺序很重要。假设每个库都包含一个名为 gag 的目标模块。在程序 foobar.f 中对 gag 的任何调用都将由第一个库 libmystuff.a 中的 gag 目标模块解析。
重要的是要记住,-l 选项应始终跟在源文件列表之后。默认情况下,f77 在搜索命令行上指定的库之前,会搜索 f2c (libf2c.so.0) 库和数学内部函数库 m (libm.so.4)。这些库提供系统调用、Fortran I/O 函数、数学内部函数和运行时初始化。
常见的 gcc 编译器错误源于编译器无法找到用户指定的库。如果发生这种情况,您将需要确定搜索库的目录。然后,将您的库重新定位到有效的库目录,或者从库目录之一创建到您的库的符号链接。您还可以使用 -L 开关将目录添加到 f77 命令行上的库路径。
上面的示例应该足以让您开始在 Linux 中使用 f77 利用 Fortran。当然,使用 f2c/gcc 组合可以提供更大的灵活性,因为有更多选项可用。(f2c 和 gcc 都有很好的 man 页面,所以这里不需要描述它们。)例如,命令
$ f2c f77demo.f
创建文件 f77demo.c,并且
$ gcc -o demoexe f77demo.c -lf2c -lm
创建可执行文件 demoexe,它们一起等效于之前显示的 f77 命令。请注意,这次我们必须显式包含 f2c 和 m 库——并且它们必须按此顺序列出。未能包含这两个库会导致编译器抱怨。例如,对于我们的示例程序,省略 -lm 选项会产生错误
Undefined symbol _exp referenced from text segment Undefined symbol _sin referenced from text segment
因为子例程 TRIG 中对 sine 和 exponential 函数的引用无法解析。
最后,您可能有兴趣尝试一个名为 fort77 的较新的 Perl 驱动程序脚本。它被标榜为 f77 的替代品,并且它包括对调试的支持,这是 f77 缺少的功能。我还没有尝试过 fort77,但它绝对在我的待办事项清单上。
有两种方法可以跟踪 Fortran 源代码中的编译错误:编译器消息和 “lints”。gcc 编译器报告的错误消息很有用,对于相对简单的程序来说,这可能就足够了。例如,假设我在第一个示例中所示的主程序中犯了一个错误,忘记放入语句
10 CONTINUE
那是第 11 行。此外,假设我也搞砸了子例程 trig 中的数组声明,键入
DIMENSION X(1)
而不是
DIMENSION X(1), Y(1)
(嘿,这可能会发生!)尝试编译这个不正确的程序,现在名为 baddemo.f,会导致以下错误序列
$ f77 -o baddemo baddemo.f f2ctmp_baddemo.f: MAIN f77demo: Error on line 17 of f2ctmp_baddemo.f: DO loop or BLOCK IF not closed Error on line 17 of f2ctmp_baddemo.f: missing statement label 10 trig: Error on line 22 of f2ctmp_baddemo.f: statement function y amid executables. Warning on line 25 of f2ctmp_baddemo.f: local variable sin never used Warning on line 25 of f2ctmp_baddemo.f: local variable exp never used
错误消息提供信息以帮助隔离问题,但行号似乎并不总是与原始 Fortran 源代码的行号相对应。这使得跟踪晦涩的错误变得有点困难,尤其是在较长的程序中。不幸的是,f77 或 f2c 中似乎没有生成带有行号的程序列表的选项。(这并不意味着它做不到!)
除了编译器错误消息之外,还有几个源代码检查器或 “lints” 可以用来帮助隔离源代码中的错误。一个易于使用的检查程序是 ftnchek。在其最简单的用法中,ftnchek 检查您的程序是否存在各种潜在错误,并且可以通过生成程序列表来简化生活。ftnchek 有很长的选项列表和详尽的 man 页面。请记住,Fortran 检查程序不会识别您程序中的所有错误。但是,检查器和 f77 错误消息的组合应该可以帮助您对抗编译错误。(当然,仔细编程也会有帮助!)
硬核(和真正有冒险精神的)程序员可以从常用的 Linux 站点获得一个名为 Toolpack 的软件包。这个大型软件包是一组程序和 C shell 脚本,提供严格的 FORTRAN 77 代码检查,以及静态和动态分析。有关 Toolpack 的描述,请参见 Fortran_FAQ(Slackware 发行版中的目录 /usr/doc/faq/lang)。
Fortran 的主要用途是在科学和工程领域,并且由于该语言已经存在了几十年,因此存在数千个高质量的程序和子例程库,其中许多是免费提供的。这些程序包括为特定目的编写的应用程序、数学子例程库、通用运行时例程以及图形绘图和显示软件包。
描述即使是可用程序的一小部分也是不可能的,但是您可以通过将 Web 浏览器指向 netlib2.cs.utk.edu 来大致了解那里有什么。该站点是 UTK/ORNL 的 Netlib 存储库,它存档了 100 多个包含数学软件、论文和数据库的软件包。Fortran 程序员将特别关注一个名为 slatec 的 “代码宝库” 集合。这是一个 “...包含超过 1400 个用 FORTRAN 77 编写的通用数学和统计例程的综合库。”(源代码,伙计们!)为了说明 slatec 库的规模,它的目录在我的磁盘上占用了 222,161 字节。
我决定使用 Linux 作为 Fortran 编程平台的一个关键因素是 PGPLOT 软件包。这个高度通用的 Fortran 库提供了 100 个原始和更高级别的子例程,用于在各种图形显示设备上绘制科学图形。例如,使用 PGPLOT 库,您可以在多个 X window 中创建多个图形,将绘图输出到 PostScript 和其他支持的打印设备,或者创建与 HPGL 格式或 Latex 图片环境兼容的文件。
除了 Linux 之外,PGPLOT 还可用于其他十二种 Unix 版本(AIX、Cray、HP、SGI、NeXT 等)、两个版本的 OpenVMS 以及使用 Microsoft Power Station 32 位 Fortran 的 MS-DOS。如果您想跨平台开发一致的 Fortran 应用程序,这种广泛的可用性是一个有吸引力的功能。我还了解到,PGPLOT 功能以编译形式 (PGPERL) 提供,可与 Perl 脚本一起使用。
下面列出的程序提供了一个简单的 PGPLOT 演示。此程序与之前给出的程序相同,添加了九行(缩进)代码以创建简单的绘图。
C============================== C Simple Program to Illustrate C PGPLOT Graphic Tools C============================== PROGRAM PGDEMO INTEGER PGBEG DIMENSION X(100), Y(100) PI=2.*ACOS(0.) N=100 IER = PGBEG(0,'?',1,1) IF (IER.NE.1) STOP CALL PGSCRN(0, 'AntiqueWhite', IER) CALL PGSCRN(1, 'MidnightBlue', IER) DO 10 I=1,N X(I)=I*(2*PI/N) 10 CONTINUE CALL TRIG(N,X,Y) CALL PGENV(0., 4., 0., .4, 0, 1) CALL PGLAB('X Values', 'Y Values', 'PGPLOT Demo') CALL PGLINE(100, X, Y) CALL PGEND DO 20 J=1,5 PRINT 15, X(J), Y(J) 15 FORMAT(2X,2F8.3) 20 CONTINUE STOP END SUBROUTINE TRIG(N,X,Y) DIMENSION X(1), Y(1) DO 10 I=1,N Y(I) = SIN(X(I))*EXP(-X(I)) 10 CONTINUE RETURN END
pgdemo 程序中的子例程 PGBEN 执行绘图初始化。将 ? 字符放在 PGBEN 参数列表中会导致程序查询要使用的输出设备。对 PGSCRN 的两次调用只是更改背景色和前景色(并且有很多颜色可供选择)。PGENV 建立 x 轴和 y 轴的限制,PGLAB 标记轴和绘图。(提供数学/希腊符号和下标/上标功能。)生成的曲线由 PGLINE 绘制,绘图由 PGEND 完成。
此 PGPLOT 程序通过命令编译
$ f77 -o pgdemo pgdemo.f -pgplot -lX11
必须在 PGPLOT 库之后包含 X11 库,因为某些 PGPLOT 子例程会创建 X window 并控制其属性。
程序执行结果显示在 图 1 中。
pgdemo 程序首先询问要使用的输出设备。我用 ? 回答,以查看可用设备列表(在安装软件包时配置)。然后我从列表中选择了 /XWINDOW,并在 X window 中绘制了 图 2 中所示的简单绘图。PGPLOT 支持很长的输出设备列表,但并非所有设备都可供 Linux 用户使用。
现在可以通过选择字体大小、轴类型、字母数字表示法和许多其他选项来真正开始乐趣了。如果您以前有使用 Fortran 绘图例程的经验,PGPLOT 将很容易学习和使用。
SciLab 是我在 Linux 下运行的最喜欢的程序之一。如果您处理矩阵或向量数据、信号分析、非线性优化、绘图或其他数学运算,您应该自己去探索这个功能丰富的程序。(SciLab 在Linux Journal 第 11 期中进行了评测,它适用于各种其他计算机平台,包括 Sun Sparc 工作站、IBM RS 6000、HP 9000、DEC Mips 和 DEC Alpha。)
除了数百个内置或提供的数学函数外,SciLab 用户还可以动态链接他们自己的 Fortran 和 C 子例程到 SciLab 二进制文件,而无需重新编译 SciLab 源代码。然后可以使用交互式命令或通过执行脚本从 SciLab 中调用链接的子例程。
这个重要功能允许 Fortran 用户(和 C 程序员)使用 “经过考验和真实” 的源代码,而无需将子例程转换为使用内置 SciLab 函数的等效 SciLab 宏和脚本的麻烦。同样重要的是,可以将繁琐的调试保持在最低限度(前提是链接的子例程已经过彻底测试)。
再次回到我们最初的简单示例代码,让我们将子例程 TRIG 链接到 SciLab 会话中。首先,我们需要使用 f77 命令编译 TRIG 目标模块
$ f77 -c trig.f
这会在当前工作目录中生成 trig.o。在 Scilab 中,我们使用 link 命令链接 trig.o
-->link('trig.o','trig') linking "trig_" defined in "trig.o " lastlink 0,0
link 命令中的第一个参数字符串是 Fortran 目标模块的名称(区分大小写)。如果此模块不在当前的 SciLab 工作目录中,则必须包含路径。第二个参数字符串必须是要链接的 Fortran 子例程的准确名称;但是,大小写不重要。(请注意,SciLab 变量区分大小写,因此在 SciLab 中后续使用 trig 时,您需要使用小写。)
也可以以相同方式链接其他子例程,并且当 link 命令在没有参数的情况下发出时,SciLab 会列出所有链接的子例程,即
-->link() ans = trig
现在我们准备在我们的 SciLab 会话中使用 trig,但首先我们需要为 trig 子例程指定两个输入变量 (n,x)。以下是使用 Scilab 回显结果发出的命令。
-->n=5 n = 5. -->x=[.1 .2 .3 .4 .5] x = ! .1 .2 .3 .4 .5 !
实际调用 trig 是使用 SciLab 的 fort 命令完成的,如下所示(以及我们示例的结果)
-->y=fort('trig',n,1,'i',x,2,'r','out',[1,5],3,'r') y = ! .0903330 .1626567 .2189268 .2610349 .2907863 !
好的,现在进行解释。在输入的表达式的左侧是子例程输出变量列表(在本例中为 y)。fort 表达式中的参数由三组组成。首先是被调用的子例程名称 (trig) 作为字符串变量。接下来是子例程输入变量的列表,在本例中给出为
n,1,'i', x,2,'r'
前三项是输入变量 (n),它在 trig 子例程参数列表中的位置 (1),以及表示变量类型的字符串字符(i 代表整数)。同样,第二个输入变量是实数数组 x,定位为 trig 中的第二个参数。此模式一直持续到列出所有输入变量为止。输入列表中的变量不必按任何特定顺序排列。换句话说,我们可以很容易地将输入列为
x,2,'r', n,1,'i'
列出所有输入变量后,我们可以指定输出变量,在本例中用
'out',[1,5],3,'r'
关键字 'out' 始终出现,后跟矩阵表示法,通知 SciLab 输出变量 y 是一个 1x5 数组。3 给出了 y 在子例程参数列表中的位置,r 说明变量类型为实数。
具有多个输出变量的子例程只需要在 fort 参数列表的输出侧列出与每个变量关联的参数,并在表达式的左侧包含每个变量。例如,假设我们有一个 Fortran 子例程,由下式给出
SUBROUTINE WIN95(IDEAS,BUGS,DOS,DELAY) REAL*4 BUGS(1,1), DOS, DELAY(1) INTEGER*2 IDEAS . . . RETURN END
输入变量是 DOS 和 IDEAS,输出是 BUGS 和 DELAY。将此子例程编译并链接到 SciLab 后,通过以下方式调用它
-->bugs,delay]=fort('win95',dos,3,'r',ideas,1,'i', 'out'[99,99],2,'r',[1,10],4,'r')
就这么简单!
当将 Fortran 子例程与 SciLab 耦合时,Fortran 子例程中的任何 print 语句都不会打印到 SciLab 会话。相反,它们会打印到您启动 SciLab 的 X window(或者如果您从窗口管理器(例如 FVWM)加载 Scilab,则打印到控制台监视器)。更重要的是,动态链接的 Fortran 子例程可以 open、read、write 和 close 磁盘文件。包含 COMMON 块的子例程必须进行重组,以通过 fort 命令行接受所有变量和常量。
Fortran 编程语言在 Linux 环境中得到了相当好的支持。此外,各种高质量的编程工具和库提供了一种能力,当与 Linux 的所有功能相结合时,为工程师和科学家提供了一个强大的编程平台。
在未来,我们可以期待具有调试器支持的强大 g77 编译器、现有支持库的持续改进以及新 Fortran 工具的发布。也许更令人兴奋的是 Linux-Lab 项目正在进行的工作。该项目正在开发驱动程序,以支持使用 Linux 获取实验室和现场数据。大多数硬件设备的高级接口将通过 C 语言库,我们希望这些库也可以从我们的 Fortran 程序中调用。
所以 大胆尝试吧, 你们这些 Fortran 狂热分子!这将是一次激动人心的冒险!
感谢 Tony Dalrymple、Rod Sobey 和 Gary Howell 对本文提出的有益意见。
Steven Hughes 博士 (s.hughes@cerc.wes.army.mil) 是密西西比州维克斯堡海岸工程研究中心的高级研究工程师。他的研究活动侧重于水波运动学、防波堤冲刷和实验室方法。他于 1994 年 10 月(内核 1.1.54)切换到 Linux,但他承认在 25 年前使用 FORTRAN 66(或者可能是 FORTRAN 1.0?)编写了他的第一个 Fortran 程序。骑自行车和两个十几岁的女儿分别让史蒂夫和他的妻子保持健康和疯狂。