首页 >

递归IO占用问题 (Recursive I/O operation)

作者:fcode  日期:02-15
来源:Fcode研讨团队
在某些时候,调用者(如主程序)在执行 write 输出语句过程中,调用一个函数。而该函数内部又有屏幕输出时,容易造成递归IO占用问题。(部分编译器产生运行时错误,如 Recursive I/O operation )

如下代码:
Program www_fcode_cn
  Implicit None
  integer func
  write(*,*) 'result=',func()
End Program www_fcode_cn

Integer Function func()
  write(*,*) '程序内write'
  func = 1
End Function func
第4行代码,输出 'result=' 后,write 屏幕语句尚未结束,再调用 func 函数。
而该函数内部又执行了 write 屏幕输出。

此时,就会发生这种错误。IVF 会出现如下运行时错误:


大图


实际上,绝大多数编译器同样会把 write 语句作为函数来对待。当 write 尚未结束时,再次执行它,就会因被占用而出错。我们可以想象一下,删除一个正在运行的 exe 程序。

这种错误比较隐蔽,很多时候不易被发现。为了避免它,我们应养成良好的习惯,function 内部尽量不使用 write 屏幕输出。如遇错误,可返回错误代码。例如,返回 1 表示成功,返回 0 表示失败等等。再由调用者根据返回值来输出错误信息。

另外,上例也可以用其他方式来避免错误发生。比如先执行 func,得到结果后,存入某变量 i ,再输出 'result=' 和变量 i。

Program www_fcode_cn
  Implicit None
  integer func , i
  i = func()
  write(*,*) 'result=',i
End Program www_fcode_cn

彭国伦的《Fortran95程序设计》一书,第八章例子 ex0829 中,其实存在这个问题,但彭老师考虑到当前话题并不在于输出占用,于是未详细介绍此问题。

其代码如下:
program  ex0829
  implicit none
  integer :: n
  integer, external :: fact
  write(*,*) 'N='
  read(*,*) n
  write(*, "(I2,'! = ',I8)" ) n, fact(n)
  stop
end

recursive integer function fact(n) result(ans)
  implicit none
  integer , intent(in) :: n
  integer, save :: count = 1
  integer :: localcount, temp  ! 局部变量
    
  localcount = count
  count = count+1
  write(6,"(I2,'th enter, n=',I2)") localcount, n

  if ( n < 0 ) then ! 不合理的输入
	ans = -1        ! 随便设定一个值
    write(6,"(I2,'th exit, n=',I2,' ans=',I8)") localcount, n, ans
	return          ! n不合理, 直接return 
  else if ( n <= 1 ) then
    ans = 1          
    write(6,"(I2,'th exit, n=',I2,' ans=',I8)") localcount, n, ans
    return          ! 不用再向下递归了, return 
  end if
  ! 会执行到这, 代表n>1, 从n*(n-1)!来计算n!
  temp = n-1
  ans = n * fact(temp) 
  write(6,"(I2,'th exit, n=',I2,' ans=',I8)") localcount, n, ans
  return
end

第7行代码调用 fact 函数,而该函数内部又有 write 语句。但读者编译这段代码时,却没有发生IO占用错误。
这是因为,在 fact 函数中,所有 write(*,...) 被换成了 write(6,...),这也正是很多读者对此处的疑问。为何写为 6,而不是 *

在 Visual Fortran 系列编译器上,6 这个通道号,代表着屏幕输出,所以,它能够正常输出。但是,并非所有编译器都是这样规定,各编译器可自行规定10以下的通道号作为保留。例如 Silverfrost Ftn95 编译器,以 -1 作为屏幕输入输出。

PS:笔者建议大家在书写代码时,不要使用10以下的文件通道号。Open 语句等,均使用 10 以上的通道号。

事实上,彭老师是通过函数内使用 6,而主程序使用 * 输出,来避免两者的占用冲突。如果把子程序改为 * 输出,而主程序改为 6 输出,也可同样避免这个错误。但这只能在 Visual Fortran 编译器上可以正常工作。

笔者认为,更合理的方式是,主程序和 fact 函数均使用 * 输出。而将代码第 7 行改为:
i = fact(n)
write(*, "(I2,'! = ',I8)" ) n, i

当然了,最理想的情况,还是避免在 function 函数中使用屏幕输出。
常规|工具|专业|读物|
代码|教学|算法|
首页 >
FortranCoder手机版-导航