【问题标题】:Reading a direct access fortran unformatted file in C++在 C++ 中读取直接访问 fortran 无格式文件
【发布时间】:2015-07-15 22:14:59
【问题描述】:

我目前正在尝试 C++ 读取 Fortran 编写的二进制文件,但没有取得多大成功。写入文件的 Fortran 代码不是我自己的,虽然 C++ 解析例程是。

二进制文件的第一条记录已使用以下语句写入:

INTEGER var1 var2 var3
WRITE(12,REC=1) var1,var2,var3

执行成功读取的 Fortran sn-p 如下所示:

open(unit=10,file="ETC.bin",access='direct',recl=24,iostat=iost,status='old')
read (unit=10,rec=1) var1,var2,var3
close(unit=10)
print*,var1,var2,var3

在 C++ 方面,到目前为止,我提出了以下几点:

FILE* binfile = fopen("ETC.bin","rb") ;
fseek (binfile,0,SEEK_END) ;
long lSize = ftell (binfile) ;
char* buffer = (char*) malloc (sizeof(char)*lSize) ;
rewind (binfile) ;
size_t result=fread(buffer,1,96,binfile) ;
for (unsigned i = 0; i<=result; i++){
   printf("%f\n",buffer[i]) ;
}

不幸的是,我的 C++ printf 语句返回的是废话。请注意,我假设 Fortran 依赖于 4 位字(例如 gfortran 编译器),并且如果使用 ifort,则

--assume byterecl

编译时需要选项。

我知道结果应该是什么,但我不确定如何在 C++ 中复制 Fortran 读取语句的行为。

感谢您的帮助!

附:这里贴了一个类似的问题:reading fortran binary file in c++,指向下面的dead link。那里没有太多信息,或者我的 Google-Fu 很糟糕。

【问题讨论】:

  • 您应该首先确认用于写入文件的计算机与读取文件的计算机是字节序兼容的。
  • 是按字还是按记录?我遇到过字节序的话题,但我认为这不会是一个问题。
  • 这将是基于单词的。如果读写计算机是相同的字节序,这可能不是问题,但如果它们不同,它将使您的程序调试变得非常困难。 This thread has a simple implementation for checking endianness
  • 你应该去阅读working with files in C++
  • 4位字应该是4字节字?

标签: c++ fortran


【解决方案1】:

我的 C 语言不是很好,但我尝试了一些东西。

首先是 Fortran 部分:

program direct_access
    implicit none
    integer, parameter :: UNT = 63347
    open(unit=UNT, file='delme.unf', access='DIRECT', &
        form='UNFORMATTED', status='REPLACE', recl=24)
    write(UNT, rec=1) 1, 2, 3
    write(UNT, rec=2) 4, 5, 6
    close(UNT)
end program direct_access

我正在将 3 个整数(每个 4 字节)写入一个记录长度为 24 字节的无格式文件。 (注意:我在这里假设记录长度以字节为单位,显然这并不能保证并且取决于编译器和系统。)

另外,来自my preferred Fortran book

未格式化的直接地址文件比格式化的直接访问文件更小更快,但它们不能在不同类型的处理器之间移植。

(除非在打开文件时特别指定FORM='FORMATTED',否则将不格式化。)

测试数据是否写入正确:

$ hexdump delme.unf
0000000 0001 0000 0002 0000 0003 0000 0000 0000
0000010 0000 0000 0000 0000 0004 0000 0005 0000
0000020 0006 0000 0000 0000 0000 0000 0000 0000
0000030

看起来不错。注意,记录长度(24字节)大于数据(3*4字节),所以里面有未使用的数据块。

现在是 C 程序,不是我的专长:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

off_t fsize(const char *filename) {
    struct stat st; 

    if (stat(filename, &st) == 0)
        return st.st_size;

    return -1; 
}

int main(){
    int record_size=24;
    int num_records=fsize("delme.unf") / record_size;
    FILE* binfile = fopen("delme.unf","rb") ;
    int* record = (int*) malloc (record_size) ;
    size_t result ;
    for (unsigned j=0; j < num_records; j++) {
        fseek(binfile, j * record_size, SEEK_SET) ;
        printf("%i : ", j) ;
        result=fread(record,sizeof(int),record_size/sizeof(int),binfile) ;
        for (unsigned i = 0; i<result; i++){
            printf("%i ",record[i]) ;
        }
        printf("\n");
    }
    free(record);
    fclose(binfile);
}

输出:

0 : 1 2 3 0 0 0 
1 : 4 5 6 0 0 0 

也不错。

我注意到的一些事情:

  • 您的缓冲区是char 类型——表示每个元素一个字节。但是整数有 4 个字节。这意味着文件内容被分成几个元素。
  • 另外,您的 fortran 代码将记录长度设置为 24(我假设为字节),但 3 个整数每个仅使用 4 个字节,因此没有使用一半的记录。这就是为什么 read 多了三个零。
  • 如果您有result 元素,则buffer 的索引需要从0 变为result-1
  • 确定文件大小的方式显然不是一个好主意,请参阅here
  • 您使用%f 作为输出,表示浮点数?但我认为这些是整数?

当然,如果你不关心乱序读取数据,你可以直接遍历文件:

#include <stdlib.h>
#include <stdio.h>

int main() {
    FILE* data = fopen("delme.unf", "rb") ;
    int var ;
    while (! feof(data )) {
        fread(&var, sizeof(int), 1, data);
        printf("%i ", var);
    }
    printf("\n");
    fclose(data);
}

肯定有人会帮助你编写比我更好的 C 代码。

【讨论】:

  • 我希望有 C 知识的人可以看看这个,因为虽然他们似乎可以工作,但如果我的 C 程序有任何好的,我会感到惊讶。
  • 真正的*8 fortran 写 + 对应的 C++ 读语句需要哪些修改?
  • int var; 变成 double varsizeof(int) 变成 sizeof(double),我想。试试看。哦,你需要一个不同的printf 声明。
【解决方案2】:

编辑请将此作为新答案

你的问题出在这部分

for (unsigned i = 0; i<=result; i++){
   printf("%f\n",buffer[i]) ;
}

首先,i&lt;=result 应该是i&lt;result。否则它从buffer 读取(结果+1)字节。这是 C/C++ 初学者的常见错误。在 C/C++ 中,如果你遍历一个有 N 个元素的数组,你会遍历从 0 到 N-1 的索引。

其次,i++ 应该是 i+=4。 Fortran INTEGER 类型和 C int 类型通常为 4 个字节。

最后,printf("%f\n",buffer[i]) 应该是 printf("%d\n",(int)buffer[i])%f in printf 采用浮点数。要打印整数,请使用%d(int)buffer[i] 使程序将缓冲区 [i] 重新解释为原始 char 类型的 int 类型。没有这个可能不会导致错误,但编译器通常会报错。

EDIT2 也许您必须使用*((int*)(&amp;buffer[i])) 而不是(int)buffer[i]

另一种方法是使用 4 字节整数数组。在这种情况下,代码如下所示。 uint32_t 是一个 4 字节的整数类型。通常int 类型是 4 个字节,但 C 标准说它可能是 2 个字节。所以使用uint32_t 是安全的。

FILE* binfile = fopen("ETC.bin","rb") ;
fseek (binfile,0,SEEK_END) ;
long lSize = ftell (binfile) ;
rewind (binfile) ;
if(lSize >= 24*sizeof(uint32_t)){
  uint32_t array[24];
  fread(array,sizeof(uint32_t),24,binfile) ;
  for (int i = 0; i<24; i++){
    printf("%d\n",array[i]) ;
  }
}else{
  printf("file size is too small.\n");
}

编辑下面的这个答案与您的问题无关。

Fortran 在未格式化模式下的写入例程会自动将页眉/页脚添加到主数据中。页眉/页脚是包含数据大小的二进制字符串。页眉/页脚的长度由编译器决定。

这是一些 fortran 程序的二进制输出。

$xdd fort.20
00000000: 4000 0000 6664 6532 6265 616d 2020 2020  @...fde2beam    
00000010: 2020 2020 2020 2020 0432 0c00 7e03 0000          .2..~...
00000020: 7e03 0000 f059 34e7 a9d5 853f 8534 5ad5  ~....Y4....?.4Z.
00000030: 0910 1340 f059 34e7 a9d5 853f 8534 5ad5  ...@.Y4....?.4Z.
00000040: 0910 1340 4000 0000

在上面的例子中,40000000 是页眉/页脚。由于 40000000 是 64,因此页眉和页脚之间必须有 64 个字节。现在在显示的十六进制文本中,您可以看到 32*4 十六进制位于页脚和页眉之间。由于 2hex = 1byte,实际上页脚/页眉之间有 64 个字节。

因此,当您让 C 程序读取 fortran 未格式化的二进制文件时,您的程序需要明智地跳过或使用这些页眉/页脚。

【讨论】:

  • 请注意,有问题的文件是直接访问。在普通编译器中,此类文件中没有任何页眉或页脚。
猜你喜欢
  • 2021-08-19
  • 1970-01-01
  • 2013-08-13
  • 2015-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-14
相关资源
最近更新 更多