在C语言中读取二进制文件

3

目前正在尝试编写用C语言读取.bin文件的程序。正如您可以从我的代码中看到的,我显然缺少了一些东西,虽然我已经尽力去阅读相关资料,但仍然完全被卡住了。正如预期的那样,我的输出结果并非预期的。我的预期输出示例应该是YV2840 KCLT KDAB Thu Jan 16 12:44:00 2014。

由于我正在尝试读取有关航班的.bin文件,以下是我认为可能出错的原因:

我应该定义一个名为“Human-readable date string”的结构体。当然,这是不可能的,因为它会生成编译器错误。也许我现在不应该字面理解它,所以我已将其定义为“时间戳”。

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

如果有人感兴趣,这里是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的返回值。你的问题可能只是因为没有成功打开文件。 - Michael Albers
每个记录占用24个字节,而您的结构体大小取决于系统中int的大小(我们不知道),因此您会遇到问题。int FlightNum [7] 明显是错误的,因为航班号是字符。建议使用明确的类型,如 int32_t 或适当的类型。 - hobbs
把那个设为48或80,我忘记在最后一个字段前加填充了。 - hobbs
2个回答

5
如果你需要读取二进制数据进入程序,那么你需要查看你尝试读取的内容。可以使用 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'(这是一个简单的双指针宏实现的重新分配 - 您也可以使用一个简单的函数)将指针数重新分配为当前数量的两倍。这里的目的只是保持代码主体干净,以便逻辑不受所有'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));
    }

这段代码涉及到it技术,其中flts[i]->tstamp被转换为time_t类型,并与其他航班数据一起使用ctime提供格式化日期输出。

将所有部分组合在一起,理解xcallocxrealloc_dp只是用于calloc和realloc的简单错误检查宏,您可以使用以下代码。 acars.bin中包含2778个航班数据,下面的代码仅打印前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个错误证实没有意外写入分配的内存块之外。

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


非常好的回答。谢谢你。 - Naikrovek
谢谢。如果今天写的话,我会删除使用xcallocxrealloc的宏,并改用函数来使代码更具可移植性。这些宏在gcc扩展中允许使用大括号宏很方便,但是这限制了可移植性。 - David C. Rankin

1
阅读二进制文件并不是一项简单的操作,因为它们依赖于编译器,无论是写入还是读取,都取决于生成数据或用于读取数据的struct的布局。
在您的二进制文件中,记录的结构看起来像这样:
0x59563238323700 (flight number 7 bytes)
0x4B434C5400 (original airport 5 bytes)
0x4B53525100 (dest airport 5 bytes)
0x000000 (3 bytes padding)
0x2C83D052 (4 bytes timestamp)

如您所见,前三个字段共计17字节,但时间戳的int数据类型要求在生成二进制数据的程序中进行4字节对齐,因此数据被填充为20字节,其中填充了0。
这意味着您必须确保您的struct布局与生成该二进制数据的布局完全相同,或者在反转原始数据格式后,逐个字段地读取并考虑填充。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接