【问题标题】:Reading a Binary file in C在 C 中读取二进制文件
【发布时间】:2015-11-15 03:22:14
【问题描述】:

目前正在尝试用 C 编写程序来读取 .bin 文件。正如您从我的代码中看到的那样,我显然遗漏了一些东西,我尝试阅读了很多内容,但仍然完全卡住了。正如预期的那样,我的输出不是预期的。我的预期输出示例是 YV2840 KCLT KDAB 2014 年 1 月 16 日星期四 12:44:00

当我试图阅读有关航空公司航班的 .bin 文件时。我认为可能是错误的原因如下。

我应该定义一个名为“人类可读日期字符串”的结构。这当然是不可能的,因为它会产生编译器错误。也许我不应该从字面上理解它,现在我将它定义为“时间戳”。

顺序和大小与写入文件的格式不匹配。

这里是bin文件,如果有人感兴趣:http://www.filedropper.com/acars 这是我的代码:

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

typedef struct MyStruct_struct {
    int FlightNum[7];
    char OriginAirportCode[5]; 
    char DestAirportCode[5];
    int TimeStamp;
} MyStruct;

int main() {
    FILE * bin;
    MyStruct myStruct;
    bin = fopen("acars.bin", "rb");

    while(1) {
        fread(&myStruct,sizeof(MyStruct),1,bin);
        if(feof(bin)!=0)
            break;
        printf("%d",myStruct.FlightNum);
        printf("%s" ,myStruct.OriginAirportCode);
        printf("%s" ,myStruct.DestAirportCode);
        printf("%d", myStruct.TimeStamp);
    }

    fclose(bin);
    return 0;
}

【问题讨论】:

  • 您需要做的第一件事是检查 fopen 和 fread 的返回值。您的问题可能就像没有成功打开文件一样简单。
  • 每条记录是 24 字节,您的结构是 42 或 74,具体取决于系统上 int 的大小(我们不知道,您可能应该使用像 int32_t 这样的显式类型或任何合适的),所以你有问题。 int FlightNum[7] 肯定是错的,因为航班号是字符。
  • 设置为 48 或 80,我忘了在最后一个字段前填充。

标签: c struct fread


【解决方案1】:

如果您要将二进制数据读入您的程序,那么您需要查看并查看您正在尝试读取的内容。 hexdumpod 是查看数据的绝佳工具:

$ hexdump -C -n 512 dat/acars.bin
00000000  59 56 32 38 32 37 00 4b  43 4c 54 00 4b 53 52 51  |YV2827.KCLT.KSRQ|
00000010  00 00 00 00 2c 83 d0 52  59 56 32 37 38 32 00 4b  |....,..RYV2782.K|
00000020  43 4c 54 00 4b 53 52 51  00 00 00 00 cc 3e ed 52  |CLT.KSRQ.....>.R|
00000030  59 56 32 37 33 32 00 4b  43 4c 54 00 4b 53 52 51  |YV2732.KCLT.KSRQ|
00000040  00 00 00 00 88 f4 d5 52  59 56 32 36 37 35 00 4b  |.......RYV2675.K|
00000050  43 4c 54 00 4b 53 52 51  00 00 00 00 20 57 9f 52  |CLT.KSRQ.... W.R|
00000060  59 34 39 38 34 31 00 4b  4d 43 4f 00 4d 4d 4d 58  |Y49841.KMCO.MMMX|

根据您的描述,您有航班号、出发机场、目的地机场和时间戳。查看数据,您会发现航班号 YV2827(以空结尾),您有 KCLT,这是夏洛特/道格拉斯国际机场的 IACO 标识符。机场,下一个 KSRQ(佛罗里达州萨拉索塔机场的 IACO 标识符),几个字节的填充,最后是一个代表时间戳的 4 字节数字。所以数据文件是有意义的。

现在怎么读?如果您的描述成立,那么保存元素的结构应该提供一种读取数据的方法。您可能必须与不同的成员和不同的 属性 合作才能使填充工作,但接近以下的东西应该可以工作:

typedef struct {
    char flight[7];
    char dept[5];
    char dest[5];
    unsigned tstamp;
} flight;

接下来,如何读取文件并将值存储在代码中的内存中。如果您不需要存储这些值,那么只需简单地读取和打印数据即可。假设您需要存储它以实际使用数据,那么在不知道acars.bin 中包含多少航班的情况下,您将需要一个方案来读取/分配内存来保存数据。

一种灵活的方法是使用静态缓冲区来读取每个航班,然后使用malloc/calloc 分配一个指向航班的指针数组,并根据需要使用realloc 来保存航班数据。比如:

    flight buf = {{0}, {0}, {0}, 0};
    flight **flts = NULL;
    size_t idx = 0;
    size_t nbytes = 0;
    ...
    /* allocate MAXS pointers to flight */
    flts = xcalloc (MAXS, sizeof *flts);

    /* read into buf until no data read, allocate/copy to flts[i] */
    while ((nbytes = fread (&buf, sizeof buf, 1, fp))) {
        flts[idx] = calloc (1, sizeof **flts);
        memcpy (flts[idx++], &buf, sizeof **flts);

        if (idx == maxs)  /* if pointer limit reached, realloc */
            flts = (flight **)xrealloc_dp((void *)flts, &maxs);
    }

上面,代码在'flts'中分配了一个初始数量的指向飞行的指针,并使用静态结构buf作为缓冲区从acars.bin文件中读取数据。在读取nbytes 且非零时,分配内存用于存储flts[idx] 中的缓冲区,memcpy 用于将数据从buf 复制到flts[idx]。 (您应该添加验证,以确保所读取的内容实际上是您所期望的)。

使用标准的重新分配方案,首先将maxs 指针分配给结构,当达到该数量时,指针的数量通过xrealloc_dp 重新分配为当前数量的两倍(这是一个简单的双精度重新分配-pointer 宏——你也可以使用一个简单的函数)这里的目的只是为了保持代码的主体干净,这样逻辑就不会被所有realloc 验证代码等所掩盖。

在完整读取 acars.bin 之后,您将所有值存储在 flts 中(注意时间戳存储为 unsigned int 值,因此转换为日历时间类型并格式化输出留待你的输出例程)。输出的简单重新格式化可能是:

    for (i = 0; i < 10; i++) {
        time_t fdate = (time_t)flts[i]->tstamp;
        printf (" flight[%4zu]  %-8s  %-5s  %-5s  %s", i, flts[i]->flight,
                flts[i]->dept, flts[i]->dest, ctime (&fdate));
    }

其中flts[i]-&gt;tstamp 被转换为time_t,然后与ctime 一起使用以提供格式化的日期以与其余的航班数据一起输出。

将所有部分放在一起,了解xcallocxrealloc_dp 只是callocrealloc 的简单错误检查宏,您可以使用类似以下的内容。 2778 包含在 acars.bin 中的航班,下面的代码仅打印前 10 个和最后 10 个航班的数据:

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

/* calloc with error check - exits on any allocation error */
#define xcalloc(nmemb, size)       \
({  void *memptr = calloc((size_t)nmemb, (size_t)size);    \
    if (!memptr) {          \
        fprintf(stderr, "error: virtual memory exhausted.\n");  \
        exit(EXIT_FAILURE); \
    }       \
    memptr; \
})

/* realloc with error check - exits on any allocation error */
#define xrealloc_dp(ptr,nmemb)   \
({ \
    void **p = ptr; \
    size_t *n = nmemb;  \
    void *tmp = realloc (p, 2 * *n * sizeof tmp);       \
    if (!tmp) { \
        fprintf (stderr, "%s() error: virtual memory exhausted.\n", __func__);  \
        exit (EXIT_FAILURE);    \
    }   \
    p = tmp;    \
    memset (p + *n, 0, *n * sizeof tmp); /* set new pointers NULL */    \
    *n *= 2;    \
    p;  \
})

#define MAXS 256

typedef struct {
    char flight[7];
    char dept[5];
    char dest[5];
    unsigned tstamp;
} flight;

int main (int argc, char **argv) {

    flight buf = {{0}, {0}, {0}, 0};
    flight **flts = NULL;
    size_t idx = 0;
    size_t nbytes = 0;
    size_t maxs = MAXS;
    size_t i, index;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* allocate MAXS pointers to flight */
    flts = xcalloc (MAXS, sizeof *flts);

    /* read into buf until no data read, allocate/copy to flts[i] */
    while ((nbytes = fread (&buf, sizeof buf, 1, fp))) {
        flts[idx] = calloc (1, sizeof **flts);
        memcpy (flts[idx++], &buf, sizeof **flts);

        if (idx == maxs)  /* if pointer limit reached, realloc */
            flts = (flight **)xrealloc_dp((void *)flts, &maxs);
    }
    if (fp != stdin) fclose (fp);

    printf ("\n There are '%zu' flights in acars data.\n", idx);

    printf ("\n The first 10 flights are:\n\n");
    for (i = 0; i < 10; i++) {
        time_t fdate = (time_t)flts[i]->tstamp;
        printf (" flight[%4zu]  %-8s  %-5s  %-5s  %s", i, flts[i]->flight,
                flts[i]->dept, flts[i]->dest, ctime (&fdate));
    }

    printf ("\n The last 10 flights are:\n\n");
    index = idx - 10;
    for (i = index; i < idx; i++) {
        time_t fdate = (time_t)flts[i]->tstamp;
        printf (" flight[%4zu]  %-8s  %-5s  %-5s  %s", i, flts[i]->flight,
                flts[i]->dept, flts[i]->dest, ctime (&fdate));
    }

    /* free memory */
    for (i = 0; i < idx; i++)
        free (flts[i]);
    free (flts);

    return 0;
}

输出

$ ./bin/readacars dat/acars.bin

 There are '2778' flights in acars data.

 The first 10 flights are:

 flight[   0]  YV2827    KCLT   KSRQ   Fri Jan 10 17:33:00 2014
 flight[   1]  YV2782    KCLT   KSRQ   Sat Feb  1 12:37:00 2014
 flight[   2]  YV2732    KCLT   KSRQ   Tue Jan 14 20:38:00 2014
 flight[   3]  YV2675    KCLT   KSRQ   Wed Dec  4 10:24:00 2013
 flight[   4]  Y49841    KMCO   MMMX   Tue Jul 23 13:25:00 2013
 flight[   5]  Y45981    KMCO   MMMX   Wed Feb 26 13:31:00 2014
 flight[   6]  Y45980    MMMX   KMCO   Tue Mar 25 13:49:00 2014
 flight[   7]  Y40981    KMCO   MMMX   Wed Mar  5 13:23:00 2014
 flight[   8]  Y40980    MMMX   KMCO   Sat Mar 29 11:38:00 2014
 flight[   9]  XX0671    KJFK   MSLP   Tue Mar 25 05:46:00 2014

 The last 10 flights are:

 flight[2768]  4O2993    KJFK   MMMX   Wed Feb 12 09:25:00 2014
 flight[2769]  1L9221    KSAT   KSFB   Thu Jan  9 15:41:00 2014
 flight[2770]  1L1761    KCID   KSFB   Tue Jan 14 13:11:00 2014
 flight[2771]  1L1625    KABE   KSFB   Thu Jan 16 10:22:00 2014
 flight[2772]  1L0751    KMFE   KSFB   Thu Jan 16 19:52:00 2014
 flight[2773]  1L0697    KTYS   KSFB   Wed Jan 15 10:21:00 2014
 flight[2774]  1L0696    KSFB   KTYS   Wed Jan 15 07:00:00 2014
 flight[2775]  1L0655    KIAG   KSFB   Fri Jan 17 21:11:00 2014
 flight[2776]  1L0654    KSFB   KIAG   Fri Jan 17 15:49:00 2014
 flight[2777]  1L0641    KGFK   KSFB   Fri Jan 17 14:21:00 2014

内存错误/泄漏检查

在您编写的动态分配内存的任何代码中,您必须使用内存错误检查程序来确保您没有写入超出分配的内存并确认您已释放所有已分配的内存。对于 Linux,valgrind 是正常的选择。滥用内存块有很多微妙的方法可能导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们使用简单。只需通过它运行您的程序即可。

$ valgrind ./bin/readacars dat/acars.bin
==12304== Memcheck, a memory error detector
==12304== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==12304== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==12304== Command: ./bin/readacars dat/acars.bin
==12304==

 There are '2778' flights in acars data.

 The first 10 flights are:

 flight[   0]  YV2827    KCLT   KSRQ   Fri Jan 10 17:33:00 2014
 flight[   1]  YV2782    KCLT   KSRQ   Sat Feb  1 12:37:00 2014
 flight[   2]  YV2732    KCLT   KSRQ   Tue Jan 14 20:38:00 2014
<snip>
 flight[2776]  1L0654    KSFB   KIAG   Fri Jan 17 15:49:00 2014
 flight[2777]  1L0641    KGFK   KSFB   Fri Jan 17 14:21:00 2014
==12304==
==12304== HEAP SUMMARY:
==12304==     in use at exit: 0 bytes in 0 blocks
==12304==   total heap usage: 2,812 allocs, 2,812 frees, 134,011 bytes allocated
==12304==
==12304== All heap blocks were freed -- no leaks are possible
==12304==
==12304== For counts of detected and suppressed errors, rerun with: -v
==12304== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

134,011 分配的字节数和所有堆块均已释放——不可能有泄漏 确认您正在释放您分配的所有内存。 错误摘要:来自 0 个上下文的 0 个错误确认在分配的内存块之外没有无意写入。

查看代码,如果您有任何问题,请告诉我,我很乐意为您提供进一步帮助。

【讨论】:

  • 答案写得很好。谢谢。
  • 谢谢。如果今天编写,我将删除使用的宏 xcallocxrealloc 并简单地使用函数来使代码更具可移植性。宏与允许大括号宏的 gcc 扩展一起使用很方便,但这限制了可移植性。
【解决方案2】:

读取二进制文件不是一个简单的操作,因为它们依赖于编译器,因为它们的结构,无论是用于写入还是读取,都取决于生成数据或用于读取数据的struct 的布局。

在您的二进制文件中,记录的结构看起来像这样:

0x59563238323700 (flight number 7 bytes)
0x4B434C5400 (original airport 5 bytes)
0x4B53525100 (dest airport 5 bytes)
0x000000 (3 bytes padding)
0x2C83D052 (4 bytes timestamp)

如您所见,前三个字段是 7+5+5 = 17 个字节,但时间戳的 int 数据类型需要在生成二进制数据的程序中对齐 4 字节,因此数据被填充到 @ 987654325@ 字节为 0。

这意味着您必须确保struct 的布局与生成该二进制数据的布局完全相同,或者在反转原始数据格式后通过考虑填充来逐字段读取它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-01
    • 2011-09-03
    • 2016-01-15
    • 1970-01-01
    • 2012-04-29
    • 2012-07-08
    • 1970-01-01
    相关资源
    最近更新 更多