1.1 早期 Fortran 与科学计算的黎明


1.1 早期 Fortran 与科学计算的黎明

本节摘要:1957 年,IBM 团队发布 FORTRAN(Formula Translation),人类第一次用接近数学的语句而非汇编指挥机器。本节回到这个起点:固定格式是穿孔卡片的物理烙印,GOTO 与 COMMON 块是那个年代的合理选择,也是后来大型工程的主要债务来源。理解这些"历史债"是怎么产生的,才能在 F90 的分水岭之后读懂为什么模块和自由格式是革命。

为什么从打孔卡讲起

今天的 Fortran 代码长什么样子,一半取决于设计者想让它变成什么,另一半取决于五十年前机器长什么样子。1957 年发布的 FORTRAN,目标只有一个:让物理学家不用写汇编。当时程序员把程序穿在 80 列的打孔卡片上,一卡一行,写完一摞卡送进读卡机。这套物理流程直接决定了语法的形态——如果今天你觉得 Fortran 的"固定格式"莫名其妙,那是你没见过读卡机。

理解这段历史不是怀旧。你迟早要面对一段 1970 年代写的模拟代码,而判断它该怎么读、该不该重构,前提就是知道它当初为什么长成那样。演化日志的第一页,从这里翻开。

固定格式:被硬件束缚的年代

FORTRAN IV 与 FORTRAN 66 时代,代码行被切成固定列区:第 1–5 列写语句标号,第 6 列写续行标记,真正的语句从第 7 列开始,且受限于 72 列。写代码像是戴着枷锁跳舞,但对当年只有几百字节内存、编译器还分不清词法的机器来说,这种规整反而降低了解析成本。

C FORTRAN 66 固定格式示例:计算前 N 个自然数的和 INTEGER N, I, SUM SUM = 0 DO 10 I = 1, N SUM = SUM + I 10 CONTINUE WRITE(*,*) 'SUM =', SUM STOP END

注意几个细节:注释以 C 开头占第一列,DO 10 是带标号的循环,循环体靠标号识别,标号必须写在 1–5 列。变量 N 没有显式声明——FORTRAN 66 有隐式类型规则,以 I 到 N 开头的变量默认是整数,其余默认是实数。这条"隐式类型"规则让无数拼错变量名的 bug 静默发生,直到 implicit none 出现才被堵住。

那个年代的另一件标志性发明是 COMMON 块:一段命名的内存区,多个程序单元可以按顺序声明各自的变量映射到同一块内存。它的效率极高——不拷贝,直接共享地址——但代价是全局状态失控。子程序 A 改了 COMMON 里的 X,子程序 B 再用 X 时毫无防备。更糟的是,如果两个单元对同一 COMMON 块的变量类型声明不一致,内存布局就错位,产生难以追踪的数值错误。规模小的时候这些问题能忍,等到气候模式、结构力学这类代码长到几十万行,维护成本就压垮了项目。

F90 分水岭:自由格式与模块

1990 年的 Fortran 90 是演化日志里最重的一页,它做了三件打破旧世界的事。第一件是自由格式:不再有列限制,空格自由,大小写不敏感,代码可以像 C 一样自然书写。第二件是模块(module):用 use 建立显式依赖,用 public/private 控制可见性,COMMON 块的全局共享有了替代品。第三件是整体数组运算:两个数组相加从三重循环变成一行 C = A + B

! Fortran 90 自由格式示例:同一件事的现代写法 program sum_first implicit none integer :: n, i, s n = 100 s = 0 do i = 1, n s = s + i end do write(*,*) 'sum =', s ! 数组版本:n 个数求和只要一行,编译器自己展开 s = sum([(i, i = 1, n)]) write(*,*) 'array sum =', s end program sum_first

implicit none 出现在第一行:它废除了隐式类型规则,所有变量必须显式声明。这条约定今天已经是铁律,任何新代码缺少它都会被同行要求补上。整体数组操作的意义不在"少打字",而在语义:C = A + B 向编译器宣告"这是逐元素运算,无数据依赖",编译器据此放心生成 SIMD 指令。而 F77 里手写循环时,编译器往往不敢激进优化,因为循环体里可能有隐藏的别名访问。

从 95 到 2023:在性能与抽象之间加杠杆

F90 之后的标准不再推翻重来,而是在三条轴上加杠杆。Fortran 95 补上纯过程(pure)与 FORALL,为并行化铺路;2003 引入面向对象(类型扩展、多态、finalizer)与 ISO_C_BINDING,让 Fortran 能与 C 双向调用;2008 加入协同数组(coarray)与 DO CONCURRENT,把分布式内存并行写进语言;2018 完善子模块与错误处理,2023 草案则把重点放在泛型模板与更细的 IEEE 浮点控制上。

! 现代 Fortran 的特征片段:用模块封装一个精度常量 module constants_mod use iso_fortran_env, only: real64 implicit none real(real64), parameter :: gravity = 9.80665_real64 end module constants_mod program demo use constants_mod, only: gravity use iso_fortran_env, only: real64 implicit none real(real64) :: t, drop t = 1.0_real64 drop = 0.5_real64 * gravity * t * t ! 自由落体位移 write(*,*) 'fall distance after 1 s =', drop, 'm' end program demo

这段代码在 F77 时代一句都写不出来:kind 常量、模块、命名导入,全是后来的产物。但它依然读起来像数学,这是 Fortran 五十年没有丢的东西。

图:标准演化时间线——从 1957 到 2023

图:标准演化时间线——从 1957 到 2023

读老代码的取舍

面对旧代码,先判断年代再决定策略:F77 及更早的代码,重点警惕隐式类型与 COMMON 块;F90–95 的代码,可以放心使用模块和数组语法;2003 之后的代码才有面向对象设施可谈。不要一上来就重写——数值代码重写最容易引入精度漂移,宁可包一层接口逐步替换。这个"先诊断、再包壳、后替换"的流程,第 8 章会完整展开。

学习目标

阅读完本节,你应当能够:

  1. 说出固定格式的列规则(1–5 列标号、第 6 列续行、第 7 列起代码)以及它的物理根源
  2. 解释 COMMON 块为什么会造成跨程序单元的隐性耦合
  3. 对比 F77 与 F90 在数组操作上的写法差异,说明整体数组语法带来的语义转变
  4. 说出 F90、F95、2003、2008、2018、2023 各标准至少一项标志性特性

本节要点回顾

  • 固定格式的根源是穿孔卡片:1–5 列标号、第 6 列续行、第 7 列起代码,是硬件物理约束的直接投影
  • 隐式类型是 F77 时代的静默 bug 源implicit none 是进入现代 Fortran 的第一道门槛
  • COMMON 块是高效的全局状态:效率高但耦合失控,被模块体系的显式依赖取代
  • F90 是演化分水岭:自由格式、模块、整体数组运算三件事一起落地
  • 整体数组运算的深层价值在语义:它告诉编译器"无数据依赖",让 SIMD 优化成为可能
  • 后续标准是杠杆不是革命:95 补纯过程,2003 加 OOP 与 C 互操作,2008 加并行,2018/2023 转向安全与泛型
  • 读老代码先判断年代:不同年代的代码适用不同的阅读与重构策略

下一节我们把镜头从历史拉回设计:为什么现代 Fortran 坚持"数组优先",又为什么敢对编译器承诺"没有别名"。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U