在C

时间:2015-11-15 03:22:15

标签: c struct fread

目前正在尝试用C编写程序来读取.bin文件。正如你可以从我的代码中看到的那样,我显然遗漏了一些东西,我试图阅读很多东西,但我仍然完全陷入困境。正如所料,我的输出并非意图。我的预期输出示例是 YV2840 KCLT KDAB Thu Jan 16 12:44:00 2014

我正在尝试阅读有关航空公司航班的.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;
}

2 个答案:

答案 0 :(得分:3)

如果要将二进制数据读入程序,则需要查看并尝试读取的内容。 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,这是Charlotte / Douglass国际机场的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指向struct的指针,当达到该数字时,指针的数量通过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));
    }

其中flts[i]->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个错误确认在分配的内存块之外没有无意的写入。

查看代码,如果您有任何问题,请与我们联系,我们将很乐意为您提供帮助。

答案 1 :(得分: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)

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

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