8.1 错误处理与健壮性设计


8.1 错误处理与健壮性设计

本节摘要:数值程序最怕的不是崩溃,而是静默算出一个错误结果——它可能跑几天才被发现,代价是天文数字。本节建立工程底线:用 error stop 让致命错误立刻失败,用 I/O 状态参数把"读文件失败"变成可处理的路径,用 allocated/associated 检查杜绝悬空访问,再用防御式编程把边界问题拦在入口。读完你会写出"宁可大声死,不可悄悄错"的代码。

静默算错比崩溃可怕

一段气候模型跑了两周,最后发现第 3 天起某个数组越界读,结果全是垃圾。崩溃至少会停下来让你知道出事,静默错误不会——它会带着错误结果继续跑,直到结论交付。数值软件健壮性的第一原则由此而来:错误必须显式、快速、大声。宁可多写几行检查,也不把命运交给"大概没事"。

error stop:让错误立刻大声

Fortran 里终止程序有两种:stop 正常终止(可带状态码),error stop 错误终止(强制结束所有并行映像,刷新输出)。并行程序里,一个映像的普通 stop 可能只停自己,其余映像还在跑——这正是用 error stop 的场景:错误是整个程序级的事件。

program robust_demo use iso_fortran_env, only: real64 implicit none integer, parameter :: n = 100 real(real64) :: a(n), scale a = 1.0_real64 read(*, *, err=100) scale ! 输入错误跳转到 100 if (scale == 0.0_real64) then error stop 'scale must not be zero' ! 致命错误,立即终止 end if a = a * scale write(*,*) sum(a) stop 'done' 100 continue error stop 'failed to read scale' end program robust_demo

error stop 后面可以带消息,编译器与运行时把它写进错误输出,退出码非零——上层脚本能据此判断失败。别用 stop 处理错误,它是"正常退出"的语义。

I/O 状态与可选错误路径

文件读取失败是数值程序最常见的错误源:输入文件缺一行、格式不对、路径不存在。老代码直接 read,失败就崩溃,还没有诊断信息。现代写法用 iostat 参数捕获状态,把 I/O 失败变成分支逻辑。

program io_robust use iso_fortran_env, only: real64 implicit none real(real64) :: vals(1000) integer :: unit, ios, n character(len=200) :: iomsg n = 0 open(newunit=unit, file='input.txt', status='old', action='read', & iostat=ios, iomsg=iomsg) if (ios /= 0) then write(*,*) 'open failed:', trim(iomsg) error stop end if do read(unit, *, iostat=ios) vals(n + 1) if (ios /= 0) exit ! 读到末尾或出错都退出 n = n + 1 if (n >= 1000) exit end do close(unit) if (n == 0) error stop 'no data read' write(*,*) 'read', n, 'values' end program io_robust

newunit=unit 让运行时分配一个空闲单元号,不用操心 5、6 被占用;iostatiomsg 组合给出错误码与人类可读信息。循环读取用 iostat 判终,是"不知道文件多长"的标准写法。

状态检查与防御式编程

访问动态内存前,先确认它真的在:allocatable 用 allocated,指针用 associated。这是第 2 章就强调的纪律,在错误处理语境里它是防线的具体形态。

subroutine safe_use(ptr, arr) use iso_fortran_env, only: real64 implicit none real(real64), pointer, intent(inout) :: ptr real(real64), allocatable, intent(inout) :: arr(:) if (associated(ptr)) then write(*,*) 'ptr ok:', ptr else error stop 'pointer not associated' end if if (.not. allocated(arr)) then write(*,*) 'arr not allocated, allocating...' allocate(arr(10)) end if arr = 0.0_real64 end subroutine safe_use

防御式编程再补三条通用检查:一是过程入口校验参数合法(尺寸为正、值在物理范围),违反就 error stop 或返回错误码;二是数组下标用边界检查选项帮助排查(gfortran 的 -fcheck=bounds,开发期开、生产期关);三是对"不可能发生的分支"也要给默认动作,别让代码掉进未定义的缝隙。检查放入口,错误的定位成本最低。

数值层面的健壮性

错误处理不只在语法层,数值层更关键。三条经验:一、除法与开方前检查分母与定义域,NaN 与 Infinity 一旦产生会像瘟疫一样传遍全场;二、迭代类算法要有最大迭代次数与收敛判据的"双保险",不收敛时显式报错而不是返回残差可疑的解;三、用 ieee_arithmetic 模块可以检测 NaN 与无穷——在大循环里周期性地"体检"数值状态,把错误拦在早期。

use ieee_arithmetic, only: ieee_is_nan, ieee_is_finite if (ieee_is_nan(x) .or. .not. ieee_is_finite(x)) then error stop 'x became NaN or infinite' end if

这条检查是廉价的哨兵,放在每步迭代末尾,能在错误数值扩散前叫停。数值错误发现得越早,排查范围越小。

学习目标

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

  1. 区分 stop 与 error stop,说出各自动作
  2. 用 iostat 参数处理文件读取失败
  3. 用 allocated 与 associated 做状态检查
  4. 说出防御式编程的三条常用检查

本节要点回顾

  • 错误必须显式快速大声:静默算错比崩溃贵得多,是数值软件第一原则
  • error stop 是致命错误的出口:并行场景会终止所有映像,带消息带非零退出码
  • iostat 与 iomsg 接管 I/O 失败:open 与 read 都有状态参数,把失败变成分支
  • 状态检查是防线形态:allocated 与 associated 在访问前确认内存真的在
  • 防御式检查放入口:参数校验、边界检查、默认分支,让错误就地暴露
  • 数值层也要体检:NaN 与无穷检测、迭代双保险,把错误拦在扩散前

错误守住了,下一步证明它"对"并且量化它"快"——测试与调优。


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