使用不同的编译器读写 Fortran 直接访问未格式化的文件

Reading writing fortran direct access unformatted files with different compilers

我在程序中有一个部分编写了一个直接访问二进制文件,如下所示:

open (53, file=filename, form='unformatted', status='unknown',
& access='direct',action='write',recl=320*385*8)
write (53,rec=1) ulat
write (53,rec=2) ulng
close(53)

这个程序是用ifort编译的。但是,如果我从用 gfortran 编译的不同程序读取数据文件,我将无法正确重建数据。如果读取数据的程序也是在ifort中编译的,那我就可以正确重构数据。这是读取数据文件的代码:

OPEN(53, FILE=fname, form="unformatted", status="unknown", access="direct", action="read", recl=320*385*8)
READ(53,REC=2) DAT

我不明白为什么会这样?我可以用两个编译器正确读取第一条记录,这是第二条记录,如果我混合使用编译器,我将无法正确重建。

默认情况下,ifort 和 gfortran 的记录长度不使用相同的块大小。在 ifort 中,open 语句中 recl 的值以 4 字节块为单位,因此您的记录长度不是 985,600 字节,而是 3,942,400 字节长。这意味着记录以 390 万字节的间隔写入。

gfortran 使用 recl 块大小为 1 字节,您的记录长度为 985,600 字节。当您读取第一条记录时,一切正常,但是当您读取第二条记录时,您会看到文件中有 985,600 字节,但数据位于文件中的 3,942,400 字节处。这也意味着您在文件中浪费了大量数据,因为您只使用了文件大小的 1/4。

有几种方法可以解决此问题:

  • 在 ifort 中以 4 字节块指定 recl,例如320*385*2 而不是 *8
  • 在 ifort 中,使用编译标志 -assume bytereclrecl 值解释为字节。
  • 在 gfortran 中补偿大小并使用 recl=320*385*32 以便您的读数正确定位。

然而,更好的方法是在 recl 单元大小中设计不可知论。您可以使用 inquire 来计算数组的 recl。例如:

real(kind=wp), allocatable, dimension(:,:) :: recltest
integer :: reclen
allocate(recltest(320,385))
inquire(iolength=reclen) recltest
deallocate(recltest)
...
open (53, file=filename, form='unformatted', status='unknown',
& access='direct',action='write',recl=reclen)
...
OPEN(53, FILE=fname, form="unformatted", status="unknown", &
access="direct", action="read", recl=reclen)

这会将 reclen 设置为存储 320x385 数组所需的值,该数组基于记录长度的编译器基本单位。如果您在编写和阅读代码时都使用它,则您的代码将适用于两种编译器,而无需在 ifort 中使用编译时标志或补偿编译器之间的硬编码 recl 差异。


说明性示例

测试用例 1

program test
  use iso_fortran_env
  implicit none

  integer(kind=int64), dimension(5) :: array
  integer :: io_output, reclen, i
  reclen = 5*8 ! 5 elements of 8 byte integers.

  open(newunit=io_output, file='output', form='unformatted', status='new', &
       access='direct', action='write', recl=reclen)

  array = [(i,i=1,5)]  
  write (io_output, rec=1) array
  array = [(i,i=101,105)]
  write (io_output, rec=2) array
  array = [(i,i=1001,1005)]
  write (io_output, rec=3) array

  close(io_output)
end program test

该程序将 5 个 8 字节整数的数组写入记录 1,2 和 3 中的文件 3 次。该数组为 5*8 字节,我将该数字硬编码为 recl 值。

使用 gfortran 5.2 的测试用例 1

我用命令行编译了这个测试用例:

gfortran -o write-gfortran write.f90

这将生成输出文件(用 od -A d -t d8 解释):

0000000                    1                    2
0000016                    3                    4
0000032                    5                  101
0000048                  102                  103
0000064                  104                  105
0000080                 1001                 1002
0000096                 1003                 1004
0000112                 1005
0000120

5 个 8 字节元素的数组连续打包到文件中,记录号 2 (101 ... 105) 从我们期望的偏移量 40 开始,这是文件中的 recl 值 5*8.

使用 ifort 16 的测试用例 1

这个编译类似:

ifort -o write-ifort write.f90

对于完全相同的代码,这会生成输出文件(用 od -A d -t d8 解释):

0000000                    1                    2
0000016                    3                    4
0000032                    5                    0
0000048                    0                    0
*
0000160                  101                  102
0000176                  103                  104
0000192                  105                    0
0000208                    0                    0
*
0000320                 1001                 1002
0000336                 1003                 1004
0000352                 1005                    0
0000368                    0                    0
*
0000480

数据都在那里,但文件中充满了 0 值元素。以 * 开头的行表示偏移量之间的每一行都是 0。第 2 条记录从偏移量 160 开始,而不是 40。请注意,160 是 40*4,其中 40 是我们指定的 5*8 的 recl。默认情况下,ifort 使用 4 字节块,因此 recl 为 40 意味着物理记录大小为 160 字节。

如果用 gfortran 编译的代码读取这个,记录 2、3 和 4 将包含所有 0 元素,并且读取记录 5 将正确读取由 ifort 写入为记录 2 的数组。让 gfortran 读取文件中的记录 2 的另一种方法是使用 recl=160 (4*5*4) 以便物理记录大小与 ifort 写入的大小相匹配。

这样做的另一个后果是浪费space。过度指定 recl 意味着您使用 4 倍的必要磁盘 space 来存储您的记录。

使用 ifort 16 和 -assume byterecl

的测试用例 1

编译为:

ifort -assume byterecl -o write-ifort write.f90

并生成输出文件:

0000000                    1                    2
0000016                    3                    4
0000032                    5                  101
0000048                  102                  103
0000064                  104                  105
0000080                 1001                 1002
0000096                 1003                 1004
0000112                 1005
0000120

这会按预期生成文件。命令行参数 -assume byterecl 告诉 ifort 将任何 recl 值解释为字节而不是双字(4 字节块)。这将产生与使用 gfortran 编译的代码匹配的写入和读取。

测试用例 2

program test
  use iso_fortran_env
  implicit none

  integer(kind=int64), dimension(5) :: array
  integer :: io_output, reclen, i
  inquire(iolength=reclen) array
  print *,'Using recl=',reclen

  open(newunit=io_output, file='output', form='unformatted', status='new', &
       access='direct', action='write', recl=reclen)

  array = [(i,i=1,5)]  
  write (io_output, rec=1) array
  array = [(i,i=101,105)]
  write (io_output, rec=2) array
  array = [(i,i=1001,1005)]
  write (io_output, rec=3) array

  close(io_output)
end program test

这个测试用例的唯一区别是我正在查询正确的 recl 来表示我的 40 字节数组(5 个 8 字节整数)。

输出

gfortran 5.2:

 Using recl=          40

ifort 16,无选项:

 Using recl=          10

ifort 16, -assume byterecl:

 Using recl=          40

我们看到,对于 gfortran 和 ifort 使用的 1 字节块,byterecl 假设 recl 是 40,这等于我们的 40 字节数组。我们还看到默认情况下,ifort 使用的 recl 为 10,这意味着 10 个 4 字节块或 10 个双字,两者都意味着 40 字节。所有这三个测试用例都产生相同的文件输出,并且来自任一编译器的 read/writes 都将正常运行。

总结

要使基于记录的、未格式化的直接数据在 ifort 和 gfortran 之间可移植,最简单的选择是将 -assume byterecl 添加到 ifort 使用的标志中。你真的应该已经这样做了,因为你是以字节为单位指定记录长度,所以这将是一个简单的改变,可能对你没有任何影响。

另一种选择是不用担心选项并使用 inquire 内在函数来查询数组的 iolength