我在程序中有一段写入直接访问二进制文件,如下所示:
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
我不明白为什么会这样?我可以使用两个编译器正确读取第一条记录,如果我混合使用编译器,它是我无法正确重建的第二条记录。
答案 0 :(得分:20)
默认情况下,Ifort和gfortran不会使用相同的块大小来记录长度。在ifort中,recl
语句中open
的值为4字节块,因此您的记录长度不是985,600字节,长度为3,942,400字节。这意味着记录以390万字节的间隔写入。
gfortran使用recl
块大小为1个字节,记录长度为985,600个字节。当您读取第一条记录时,一切正常,但是当您读取第二条记录时,您会查看文件中的985,600字节,但数据是3,942,400字节到文件中。这也意味着您在文件中浪费了大量数据,因为您只使用了其大小的1/4。
有几种方法可以解决这个问题:
320*385*2
代替*8
-assume byterecl
将recl
值解释为字节。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差异来补偿编译器。
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 -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-bye元素的数组被连续打包到文件中,记录号2(101 ... 105
)从我们期望的偏移量40开始,这是文件{{1}中的recl值}}
这是类似的编译:
5*8
对于完全相同的代码,这会产生输出文件(用ifort -o write-ifort write.f90
解释):
od -A d -t d8
数据全部存在,但文件中充满了0值元素。以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.记录号2从偏移160开始而不是40.注意160是40 * 4,其中40是我们指定的*
的recl。默认情况下,ifort使用4字节块,因此recl为40表示物理记录大小为160字节。
如果使用gfortran编译的代码读取此内容,则记录2,3和4将包含所有0个元素,而记录5的读取将正确读取由ifort写入记录2的数组。将gfortran读取记录2放在文件中的替代方法是使用5*8
(4 * 5 * 4),以便物理记录大小与ifort写入的大小匹配。
这样做的另一个后果是浪费空间。过度指定recl意味着您使用4倍的必要磁盘空间来存储记录。
recl=160
编译为:
-assume byterecl
并生成输出文件:
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
告诉ifort将任何-assume byterecl
值解释为字节而不是双字(4字节块)。这将产生与gfortran编译的代码匹配的写入和读取。
recl
这个测试用例的唯一区别是我正在查询正确的recl来表示我的40字节数组(5个8字节整数)。
gfortran 5.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
ifort 16,没有选项:
Using recl= 40
ifort 16, Using recl= 10
:
-assume byterecl
我们看到gfortran和ifort使用的1字节块, Using recl= 40
假设recl是byterecl
,它等于我们的40字节数组。我们还看到,默认情况下,ifort使用recl为10,这意味着10个4字节块或10个双字,两者都意味着40个字节。所有这三个测试用例都可以生成相同的文件输出,并且任何一个编译器的读/写都可以正常工作。
要在ifort和gfortran之间移植基于记录的,未格式化的直接数据,最简单的选择就是将40
添加到ifort使用的标志中。你真的应该这样做,因为你是以字节为单位指定记录长度,所以这将是一个直接的改变,可能对你没有任何影响。
另一种方法是不用担心该选项,并使用-assume byterecl
内在函数来查询数组的inquire
。