Ваша проблема представляет классическую проблему: «Как мне прочитать и выделить для Х числа что-то, когда я не знаю, сколько заранее?»На самом деле это более простой вариант вопроса, потому что вы можете прочитать число X
в первой строке вашего файла данных.
(что упрощает задачу до одного размещения структур X после прочтения первой строки - в противном случае вам нужно будет отслеживать текущее количество выделенных структур и realloc
при необходимости)
Для начала я бы рекомендовал не создавать char entry[1024];
в вашей структуре по двум причинам: во-первых, автоматическое хранилище для entry
создается в стеке, а большой дневник может легко StackOverflow ... Во-вторых, это просто расточительно.Если целью является динамическое распределение, выделите только хранилище, необходимое для каждого entry
.Вы можете объявить один буфер из 1024
символов для использования в качестве буфера чтения, но затем выделить только strlen (buf) + 1
символ для хранения записи (после обрезки включенного '\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;
}
( примечание: вы можете еще больше упростить код, используя POSIX getline()
для чтения вместо фиксированного buf
, и вы можете упростить выделение и копирование каждой записи в вашей структуре, используя strdup()
, но ни один из них не гарантированно доступен для всех компиляторов - используйте их, если ваш компилятор поддерживает их, и переносимость везде не имеет значения. Также обратите внимание, что GNU gcc использует %zu
в качестве спецификатора формата для size_t
. Если выдля windoze измените значение на %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) он может быть освобожден , когда он больше не нужен.
Обязательно, чтобывы используете программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаетесь прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всепамять, которую вы выделили.
Для Linux valgrind
- нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.
$ 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];
)
Всегда подтверждайте, что вы освободили всю выделенную память и нет ошибок памяти.
MS 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
( note: Я поставил свой.obj файлы в подкаталоге ./obj
и мои двоичные исполняемые файлы в ./bin
для поддержания моего исходного каталога в чистоте. Это цель /Foobj/diary
и /Febin/diary
выше)
Пример Use /Выход
> 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.
Просмотрите все и дайте мне знать, если у вас есть дальнейшиевопросы.