您的问题提出了经典问题“当我事先不知道有多少东西时,如何读取和分配 X 数量的东西?” 这实际上是问题的一个更简单的变体,因为您可以将X数字作为数据文件的第一行读取。
(在读取第一行后将问题简化为 X 结构的单个分配 - 否则您需要跟踪当前分配的结构数量并realloc根据需要)
首先,我建议不要char entry[1024];在您的结构中创建,原因有两个 - 首先,自动存储entry是在堆栈上创建的,并且大型日记很容易 StackOverflow ......其次,这只是浪费。如果目标是动态分配,则仅分配每个entry. 您可以声明一个1024字符缓冲区以用作读取缓冲区,但随后仅分配strlen (buf) + 1char 来保存条目(在从条目中修剪包含的内容之后'\n')。
您的问题的其余部分是任何可靠代码的基础,只是验证每次读取、每次解析和每次分配,以确保您正在处理有效数据并在整个代码中具有有效存储。这适用于您编写的每一段代码,而不仅仅是这个问题。
将这些部分放在一起,并在下面的评论中提供更多详细信息,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct journal {
int day,
month,
year;
char *entry; /* declare a pointer, allocate only need No. of chars */
} diary_t;
#define MAXLENGTH 1024 /* max read buf for diary entry */
int main (int argc, char **argv) {
size_t entries = 0, i, n = 0;
char buf[MAXLENGTH] = "";
diary_t *diary = NULL;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
/* read first line, parse number of entries */
if (!(fgets (buf, MAXLENGTH, fp)) || /* validate read */
sscanf (buf, "%zu", &entries) != 1) { /* validate conversion */
fputs ("error: failed to read 1st line.\n", stderr);
return 1;
}
/* allocate/validate entries number of diary_t */
if (!(diary = calloc (entries, sizeof *diary))) {
perror ("calloc-diary_pointers");
return 1;
}
for (i = 0; i < entries; i++) { /* loop No. entries times */
size_t len = 0;
if (!fgets (buf, MAXLENGTH, fp)) { /* read/validate date */
fprintf (stderr, "error: failed to read date %zu.\n", i);
return 1;
}
if (sscanf (buf, "%d/%d/%d", /* parse into day, month, year */
&diary[i].day, &diary[i].month, &diary[i].year) != 3) {
fprintf (stderr, "error failed to parse date %zu.\n", i);
return 1;
}
if (!fgets (buf, MAXLENGTH, fp)) { /* read entry */
fprintf (stderr, "error: failed to read entry %zu.\n", i);
return 1;
}
len = strlen (buf); /* get length */
if (len && buf[len - 1] == '\n') /* check last char is '\n' */
buf[--len] = 0; /* overwrite with nul-character */
else if (len == MAXLENGTH - 1) { /* check entry too long */
fprintf (stderr, "error: entry %zu exceeds MAXLENGTH.\n", i);
return 1;
}
/* allocate/validate memory for entry */
if (!(diary[i].entry = malloc ((len + 1)))) {
perror ("malloc-diary_entry");
fprintf (stderr, "error: memory exausted, entry[%zu].\n", i);
break; /* out of memory error, don't exit, just break */
}
strcpy (diary[i].entry, buf); /* copy buf to entry */
n++; /* increment successful entry read */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (i = 0; i < n; i++) { /* output diary entries */
printf ("entry[%2zu]: %2d/%2d/%4d - %s\n", i, diary[i].day,
diary[i].month, diary[i].year, diary[i].entry);
free (diary[i].entry); /* don't forget to free entries */
}
free (diary); /* don't forget to free diary */
return 0;
}
(注意:您可以通过使用 POSIXgetline()进行读取而不是固定来进一步简化代码,buf并且您可以使用 简化每个条目的分配和复制到您的结构中strdup(),但不能保证所有编译器都可用 - 如果您的编译器使用它们支持它们并且无处不在的可移植性不是问题。还要注意 GNU gcc%zu用作size_t.%lu
示例输入文件
$ cat dat/diary.txt
4
12/04/2010
Interview went well I think, though was told to wear shoes.
18/04/2010
Doc advised me to concentrate on something... I forget.
03/05/2010
Was asked today if I was an art exhibit.
19/05/2010
Apparently mudcakes not made of mud, or angry wasps.
示例使用/输出
$ ./bin/diary <dat/diary.txt
entry[ 0]: 12/ 4/2010 - Interview went well I think, though was told to wear shoes.
entry[ 1]: 18/ 4/2010 - Doc advised me to concentrate on something... I forget.
entry[ 2]: 3/ 5/2010 - Was asked today if I was an art exhibit.
entry[ 3]: 19/ 5/2010 - Apparently mudcakes not made of mud, or angry wasps.
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2 个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在它不存在时被释放更需要。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化的值进行条件跳转,最后确认释放所有分配的内存。
对于 Linuxvalgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/diary <dat/diary.txt
==6403== Memcheck, a memory error detector
==6403== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6403== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==6403== Command: ./bin/diary
==6403==
entry[ 0]: 12/ 4/2010 - Interview went well I think, though was told to wear shoes.
entry[ 1]: 18/ 4/2010 - Doc advised me to concentrate on something... I forget.
entry[ 2]: 3/ 5/2010 - Was asked today if I was an art exhibit.
entry[ 3]: 19/ 5/2010 - Apparently mudcakes not made of mud, or angry wasps.
==6403==
==6403== HEAP SUMMARY:
==6403== in use at exit: 0 bytes in 0 blocks
==6403== total heap usage: 5 allocs, 5 frees, 309 bytes allocated
==6403==
==6403== All heap blocks were freed -- no leaks are possible
==6403==
==6403== For counts of detected and suppressed errors, rerun with: -v
==6403== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
(注意:所有日记条目(整个日记)所需的存储空间仅309-bytes小于1/10th声明所需的存储空间char entry[1024];)
始终确认您已释放所有已分配的内存并且没有内存错误。
微软视窗
由于您似乎在 windows 上遇到问题,以下是上面的代码,除了%lu替换%zu(因为 windows 将%zu其视为文字),在 Win7 上使用旧版本的 VS 编译器编译:
> cl /?
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
编译
> cl /nologo /Wall /wd4706 /wd4996 /Ox /Foobj/diary /Febin/diary /Tc diary.c
(注意:我把我的 .obj 文件./obj和我的二进制可执行文件放在一个子目录中,以保持我的源目录干净./bin。这就是上面的目的)/Foobj/diary/Febin/diary
示例使用/输出
> bin\diary.exe dat\diary.txt
entry[ 0]: 12/ 4/2010 - Interview went well I think, though was told to wear shoes.
entry[ 1]: 18/ 4/2010 - Doc advised me to concentrate on something... I forget.
entry[ 2]: 3/ 5/2010 - Was asked today if I was an art exhibit.
entry[ 3]: 19/ 5/2010 - Apparently mudcakes not made of mud, or angry wasps.
您必须确保您每次都%zu进行更改,%lu否则您无法期望正确的输出。您说您已将所有内容更改为int,但您在下面的评论中发布的片段包含%zu- 这在 Windows 上不起作用。
如果您还有其他问题,请仔细查看并告诉我。