繁体   English   中英

当我不知道二进制文件中的数据量时如何使用 Fread 和 Fwrite

[英]How to use Fread and Fwrite when i don't know the amount of data in a binary file

我知道fwritefread的理论,但我一定犯了一些错误,因为我不能让它们工作。 我制作了一个随机结构,初始化了一个数组并使用fwrite将其保存到二进制文件中。 然后我打开同一个二进制文件,使用fread并将里面的内容保存在另一个数组中。 使用调试器,我看到了第二个数组里面的内容,它说,例如:

ParkingLot2[0].company= "ffffffffffffffff"
ParkingLot2[0].years=-842150451. 

粘贴代码时,我删除了所有使代码过长的内容,例如用于控制文件打开的if (f==NULL)和 malloc 之后的pointer==NULL以及freadfwrite读取正确数量的控件数据的。

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


typedef struct Cars {
    int years;
    char company[16];
}Tcars;

void WriteBinaryFile(char* file_name, Tcars* p_1)
{
    FILE* f;
    f = fopen(file_name, "wb");
    fwrite(p_1, sizeof(Tcars), 3, f);
    fclose(f);
    return;
}

Tcars* ReadBinaryFile(char* file_name, Tcars* p_2, int* pc)
{
    FILE* f;
    Tcars temp;
    size_t number = 1;
    f = fopen("cars.dat", "rb");
        while (number) {
            number = fread(&temp, sizeof(Tcars), 1, f);
            if (number)
                (*pc)++;
        }
/*i already know that the size is 3 but i want to try this method because in my last exam i
 was given a .dat file from my professor and i didn't know how much data i had to read through */
        if ((*pc) != 0)
        {
            p_2 = malloc(sizeof(Tcars) * (*pc));
            fread(p_2, sizeof(Tcars), (*pc), f);
        }
        fclose(f);
    return p_2;
}

int main()
{
    Tcars* ParkingLot1 = malloc(sizeof(Tcars) * 3);
    for(int i=0;i<3;i++)
    {
        ParkingLot1[i].years = 2000 + i;
    }
    strcpy(ParkingLot1[0].company, "Fiat");
    strcpy(ParkingLot1[1].company, "Ford");
    strcpy(ParkingLot1[2].company,"Toyota");
    Tcars* ParkingLot2 = NULL;
    int cars_amount = 0;
    WriteBinaryFile("cars.dat", ParkingLot1);
    ParkingLot2 = ReadBinaryFile("cars.dat", ParkingLot2, &cars_amount);
    free(ParkingLot1);
    free(ParkingLot2);
    return 0;
}

您有许多小(和一些不那么小的错误)会导致您出现问题。 您的主要问题是将Tcars* p_2传递给ReadBinaryFile()并使用p_2 = malloc(sizeof(Tcars) * (*pc));进行分配每一次。 为什么?

malloc()的每次调用都会返回一个具有不同地址的新内存块。 每次新调用都会覆盖p_2的地址,从而造成内存泄漏并丢失上次调用之前存储的数据。 相反,您需要realloc()重新分配更大的内存块并将现有数据复制到新的更大块,以便您可以在重新分配的块的末尾添加下一个Tcars价值的数据。

如果您从一个文件中读取数据,则无需将p_2作为参数传递。 只需在ReadBinaryFile()realloc()中声明一个新指针,并在末尾添加每个Tcars值的数据,然后返回新分配的指针。 (您必须验证每个分配和重新分配)

您为WriteBinaryFile()选择的void将隐藏创建或写入新文件时遇到的任何错误。 您必须验证每个输入/输出文件操作,特别是如果写入的数据稍后将在您的程序中使用。 一个简单的返回类型int的选择,返回0表示失败,返回1表示成功(反之亦然,由您决定)就是您所需要的。 这样您就可以在文件创建或写入过程中处理任何错误,而不是盲目地假设成功。

一个稍微更微妙的问题/错误是您使用malloc()分配ParkingLot1 理想情况下,您将使用calloc()或使用memset将所有字节设置为零。 为什么? 您将整个Tcars结构写入文件。 malloc()不会初始化分配的内存。 这意味着company名称中在名称结尾(nul 终止字符)和16字节存储空间结尾之间的所有字符都将未初始化。 虽然这不会导致您的读取或写入出现问题,但最好确保写入文件的所有数据都是初始化数据。 否则检查文件的内容将显示写入文件的未初始化值块。

另一个小风格问题是指针声明中的'*'通常与变量而不是类型一起使用。 为什么?

    Tcars* a, b, c;

上面的声明肯定没有声明Tcars类型的 3 指针,而是声明了指针a和两个具有自动存储持续时间bcTcars类型的结构。 写作:

    Tcars *a, b, c;

清楚地说明了这一点。

最后,不要在函数中使用MagicNumbers或硬编码文件名。 您不需要为了读取或写入不同的文件名而重新编译代码。 可以在main()中使用"cars.dat"作为默认文件名,但可以将文件名作为程序的第一个参数(这就是 main main()int argc, char **argv参数)或提示用户获取文件名并将其作为输入。 316MagicNumbers 如果您需要一个常量,请#define它们或使用全局enum

总而言之,您可以执行类似于以下的操作来从数据文件中读取未知数量的Tcars

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

#define NCARS 3       /* if you need a constant, #define one (or more) */
#define COMPANY 16

typedef struct Cars {
    int years;
    char company[COMPANY];
} Tcars;

/* returns 1 on success 0 on error */
int WriteBinaryFile (char *file_name, Tcars *p_1, size_t nelem)
{
  FILE *f;
  size_t size = sizeof *p_1;
  
  f = fopen (file_name, "wb");
  if (!f) {   /* validate file open for writing */
    perror ("fopen-file_name-write");
    return 0;
  }
  
  /* validate that nelem blocks of size are written to file */
  if (fwrite (p_1, size, nelem, f) != nelem) {
    return 0;
  }
  
  if (fclose (f)) {       /* validate close-after-write */
    perror ("fclose-f");
    return 0;
  }
  
  return 1;
}

/* returns pointer to allocated block holding ncars cars on success,
 * NULL on failure to read any cars from file_name.
 */
Tcars *ReadBinaryFile (char *file_name, size_t *ncars)
{
  FILE *f;
  
  Tcars *tcars = NULL, temp;
  size_t nelem = *ncars, size = sizeof (Tcars);
  
  f = fopen (file_name, "rb");
  if (!f) {
    perror ("fopen-file_name-read");
    return NULL;
  }
  
  while (fread (&temp, size, 1, f) == 1) {
    /* always realloc to a temporary pointer */
    void *tempptr = realloc (tcars, (nelem + 1) * size);
    if (!tempptr) {   /* validate realloc succeeds or handle error */
      perror ("realloc-tcars");
      break;
    }
    tcars = tempptr;                      /* assign reallocated block */
    memcpy (tcars + nelem, &temp, size);  /* copy new car to end of block */
    nelem += 1;
  }
  
  fclose (f);
  *ncars = nelem;
  
  return tcars;
}

/* void is fine for print functions with no bearing on the 
 * continued operation of your code.
 */
void prn_cars (Tcars *cars, size_t nelem)
{
  for (size_t i = 0; i < nelem; i++) {
    printf ("%4d  %s\n", cars[i].years, cars[i].company);
  }
}

int main (int argc, char **argv)
{
  /* read from filename provided as 1st argument ("cars.dat" by default) */
  char *filename = argc > 1 ? argv[1] : "cars.dat";
  /* must use calloc() on ParkingLot1 or zero memory to avoid writing
   * unintialized characters (rest of company) to file.
   */
  Tcars *ParkingLot1 = calloc (NCARS, sizeof(Tcars)),
        *ParkingLot2 = NULL;
  size_t cars_amount = 0;
  
  if (!ParkingLot1) {   /* validate EVERY allocation */
    perror ("calloc-ParkingLot1");
    return 1;
  }
  
  for (int i = 0; i < NCARS; i++) {
    ParkingLot1[i].years = 2000 + i;
  }
  
  strcpy (ParkingLot1[0].company, "Fiat");
  strcpy (ParkingLot1[1].company, "Ford");
  strcpy (ParkingLot1[2].company,"Toyota");
  
  /* validate WriteBinaryFile succeeds or handle error */
  if (!WriteBinaryFile (filename, ParkingLot1, NCARS)) {
    return 1;
  }
  
  ParkingLot2 = ReadBinaryFile (filename, &cars_amount);
  
  if (ParkingLot2) {  /* validate ReadBinaryFile succeeds or handle error */
    prn_cars (ParkingLot2, cars_amount);    /* output cars read from file */
    free (ParkingLot2);                     /* free if ParkingLot2 not NULL */
  }
  
  free(ParkingLot1);  /* free ParkingLot1 */
}

注意:您总是在写入后检查fclose()的返回,以捕获任何文件错误和将数据刷新到在fwrite()调用时无法捕获的文件的错误)

另请注意,在freadfwrite的手册页中,它们可以读取或写入的字节数少于您请求的字节数(或元素数)。 短读可能表示也可能不表示错误或文件过早结束,您需要调用ferror()feof()来确定发生了哪些(如果有)。 虽然从磁盘直接读取文件不像网络读取和写入那样容易发生短读,但无论从何处读取或写入数据,完整的实现都可以防止短读。 进一步的调查留给你。

示例使用/输出

$ ./fwrite_fread_cars dat/cars.dat
2000  Fiat
2001  Ford
2002  Toyota

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2 个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在它不存在时被释放更需要。

您必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化值的条件跳转,最后确认释放所有分配的内存。

对于 Linux valgrind是正常的选择。 每个平台都有类似的内存检查器。 它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./fwrite_fread_cars dat/cars.dat
==7237== Memcheck, a memory error detector
==7237== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7237== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==7237== Command: ./fwrite_fread_cars dat/cars.dat
==7237==
2000  Fiat
2001  Ford
2002  Toyota
==7237==
==7237== HEAP SUMMARY:
==7237==     in use at exit: 0 bytes in 0 blocks
==7237==   total heap usage: 9 allocs, 9 frees, 10,340 bytes allocated
==7237==
==7237== All heap blocks were freed -- no leaks are possible
==7237==
==7237== For lists of detected and suppressed errors, rerun with: -s
==7237== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放所有已分配的内存并且没有内存错误。

看看事情,如果你有任何问题,请告诉我。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM