[英]How to use Fread and Fwrite when i don't know the amount of data in a binary file
我知道fwrite
和fread
的理论,但我一定犯了一些错误,因为我不能让它们工作。 我制作了一个随机结构,初始化了一个数组并使用fwrite
将其保存到二进制文件中。 然后我打开同一个二进制文件,使用fread
并将里面的内容保存在另一个数组中。 使用调试器,我看到了第二个数组里面的内容,它说,例如:
ParkingLot2[0].company= "ffffffffffffffff"
ParkingLot2[0].years=-842150451.
粘贴代码时,我删除了所有使代码过长的内容,例如用于控制文件打开的if (f==NULL)
和 malloc 之后的pointer==NULL
以及fread
和fwrite
读取正确数量的控件数据的。
#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
和两个具有自动存储持续时间b
和c
的Tcars
类型的结构。 写作:
Tcars *a, b, c;
清楚地说明了这一点。
最后,不要在函数中使用MagicNumbers或硬编码文件名。 您不需要为了读取或写入不同的文件名而重新编译代码。 可以在main()
中使用"cars.dat"
作为默认文件名,但可以将文件名作为程序的第一个参数(这就是 main main()
的int argc, char **argv
参数)或提示用户获取文件名并将其作为输入。 3
和16
是MagicNumbers 。 如果您需要一个常量,请#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()
调用时无法捕获的文件的错误)
另请注意,在fread
和fwrite
的手册页中,它们可以读取或写入的字节数少于您请求的字节数(或元素数)。 短读可能表示也可能不表示错误或文件过早结束,您需要调用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.