使用不同的编译器读写 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 byterecl
将 recl
值解释为字节。
- 在 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
。
我在程序中有一个部分编写了一个直接访问二进制文件,如下所示:
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 byterecl
将recl
值解释为字节。 - 在 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
。