«Передовой опыт» несколько субъективен, но цель «всегда подтверждено, логична и читабельна» всегда должна быть целью.
Для чтения фиксированного количества полей (в вашем случае выбрав cols 1, 2, 5
в качестве строковых значений неизвестной длины) и cols 3, 4
в качестве простых int
значений), вы можете прочитать неизвестное количество строк из файла просто выделение хранилища для некоторого разумно ожидаемого количества строк данных, отслеживание количества заполненных строк, а затем перераспределение хранилища по мере необходимости, когда вы достигнете предела выделенного хранилища.
Эффективным способом обработки перераспределения является перераспределение с помощью некоторого разумного количества дополнительных блоков памяти, когда требуется перераспределение (вместо выполнения вызовов realloc
для каждой дополнительной строки данных). Вы можете либо добавить фиксированное количество новых блоков, умножить то, что у вас есть, на 3/2
или 2
, либо на какую-то другую разумную схему, соответствующую вашим потребностям. Обычно я просто удваиваю память каждый раз, когда достигается предел выделения.
Поскольку у вас есть фиксированное количество полей неизвестного размера, вы можете упростить задачу, просто разделив пять полей с помощью sscanf
и подтвердив, что 5 преобразований имели место, проверив возврат sscanf
. Если бы вы читали неизвестное количество полей, то вы бы просто использовали ту же схему перераспределения, чтобы обрабатывать раскрытие по столбцам, рассмотренное выше, для чтения неизвестного количества строк.
(в этом случае не требуется, чтобы в каждой строке было одинаковое количество полей, но вы можете принудительно выполнить проверку, установив переменную, содержащую количество полей, прочитанных в первой строке, и затем проверив, что все последующие строки имеют тот же номер ...)
Как обсуждалось в комментариях, чтение строки данных с помощью строчно-ориентированной функции ввода, такой как fgets
или POSIX getline
, и последующий анализ данных с помощью токенизации с strtok
или в этом случае с фиксированным числом полей простой анализ с sscanf
- это надежный подход. Это обеспечивает возможность независимой проверки (1) чтения данных из файла; и (2) разбора данных на необходимые значения. (хотя и менее гибкий, для некоторых наборов данных вы можете сделать это за один шаг с fscanf
, но это также создает проблемы scanf
для пользовательского ввода того, что остается непрочитанным во входном буфере, в зависимости от преобразования -спецификаторы б / у ...)
Самый простой способ приблизиться к хранилищу ваших 5 полей - объявить простое struct
. Поскольку число символов для каждого из символьных полей неизвестно, члены структуры для каждого из этих полей будут указателем символов, а остальные поля int
, например,
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRSZ 2 /* use 8 or more, set to 2 here to force realloc */
#define MAXC 1024
typedef struct {
char *col1, *col2, *col5;
int col3, col4;
} mydata_t;
Теперь вы можете начать выделение для обработки неизвестного количества из них, выделив некоторую разумно ожидаемую сумму (я бы обычно использовал 8
или 16
со схемой удвоения, поскольку она будет расти достаточно быстро), но мы имеем выберите 2
здесь с #define ARRSZ 2
, чтобы убедиться, что мы принудительно перераспределим при обработке вашего файла данных из 3 строк Также обратите внимание, что мы устанавливаем максимальное количество символов в строке из #define MAXC 1024
для ваших данных ( не экономьте на размере буфера )
Для начала все, что нам нужно сделать, это объявить буфер для хранения каждой строки и несколько переменных для отслеживания текущего количества распределенных структур, счетчик строк (для вывода точных сообщений об ошибках) и счетчик для числа строк данных, которые мы заполнили. Затем, когда (rows_filled == allocated_array_size)
вы realloc
, например,
int main (int argc, char **argv) {
char buf[MAXC];
size_t arrsz = ARRSZ, line = 0, row = 0;
mydata_t *data = NULL;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* allocate an 'arrsz' initial number of struct */
if (!(data = malloc (arrsz * sizeof *data))) {
perror ("malloc-data");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line from file */
char c1[MAXC], c2[MAXC], c5[MAXC]; /* temp strings for c1,2,5 */
int c3, c4; /* temp ints for c3,4 */
size_t len = strlen (buf); /* length for validation */
line++; /* increment line count */
/* validate line fit in buffer */
if (len && buf[len-1] != '\n' && len == MAXC - 1) {
fprintf (stderr, "error: line %zu exceeds MAXC chars.\n", line);
return 1;
}
if (row == arrsz) { /* check if all pointers used */
void *tmp = realloc (data, arrsz * 2 * sizeof *data);
if (!tmp) { /* validate realloc succeeded */
perror ("realloc-data");
break; /* break, don't exit, data still valid */
}
data = tmp; /* assign realloc'ed block to data */
arrsz *= 2; /* update arrsz to reflect new allocation */
}
( примечание: при вызове realloc
, вы никогда не перераспределяете сам указатель , например, data = realloc (data, new_size);
Если realloc
не удается (и это происходит), он возвращает NULL
который перезапишет ваш оригинальный указатель, что приведет к утечке памяти. Всегда realloc
с временным указателем, проверьте, затем назначьте новый блок памяти вашему исходному указателю)
Остается только разделить строку на наши значения, обработать любые ошибки в формате строки, добавить значения нашего поля в наш массив struct, увеличить количество строк / строк и повторять до тех пор, пока у нас не кончатся данные для чтения, например
/* parse buf into fields, handle error on invalid format of line */
if (sscanf (buf, "%1023s %1023s %d %d %1023s",
c1, c2, &c3, &c4, c5) != 5) {
fprintf (stderr, "error: invalid format line %zu\n", line);
continue; /* get next line */
}
/* allocate copy strings, assign allocated blocks to pointers */
if (!(data[row].col1 = mystrdup (c1))) { /* validate copy of c1 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
if (!(data[row].col2 = mystrdup (c2))) { /* validate copy of c2 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
data[row].col3 = c3; /* assign integer values */
data[row].col4 = c4;
if (!(data[row].col5 = mystrdup (c5))) { /* validate copy of c5 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
row++; /* increment number of row pointers used */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
puts ("values stored in struct\n");
for (size_t i = 0; i < row; i++)
printf ("%-4s %-10s %4d %4d %s\n", data[i].col1, data[i].col2,
data[i].col3, data[i].col4, data[i].col5);
freemydata (data, row);
return 0;
}
И мы закончили (за исключением использования памяти / проверки ошибок)
Обратите внимание, что есть две вспомогательные функции, которые нужно выделить для каждой строки и скопировать каждую строку в выделенный ей блок памяти, а также присвоить начальный адрес этого блока нашему указателю в нашей структуре. mystrdup()
Вы можете использовать strdup()
, если он у вас есть, я просто включил функцию, чтобы показать вам, как вручную обрабатывать malloc
и копировать. Примечание: как выполняется копирование с memcpy
вместо strcpy
- Почему? Вы уже отсканировали вперед в строке, чтобы найти '\0'
, когда нашли длину с strlen
- не нужно, чтобы strcpy
повторил этот процесс снова - просто используйте memcpy
.
/* simple implementation of strdup - in the event you don't have it */
char *mystrdup (const char *s)
{
if (!s) /* validate s not NULL */
return NULL;
size_t len = strlen (s); /* get length */
char *sdup = malloc (len + 1); /* allocate length + 1 */
if (!sdup) /* validate */
return NULL;
return memcpy (sdup, s, len + 1); /* pointer to copied string */
}
Последняя вспомогательная функция - freemydata()
, которая просто вызывает free()
в каждом выделенном блоке, чтобы гарантировать, что вы освободите всю выделенную память. Это также держит ваш код в порядке. (вы можете сделать то же самое для блока realloc
, чтобы переместить его в свою собственную функцию)
/* simple function to free all data when done */
void freemydata (mydata_t *data, size_t n)
{
for (size_t i = 0; i < n; i++) { /* free allocated strings */
free (data[i].col1);
free (data[i].col2);
free (data[i].col5);
}
free (data); /* free structs */
}
Соединение всех частей даст вам:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRSZ 2 /* use 8 or more, set to 2 here to force realloc */
#define MAXC 1024
typedef struct {
char *col1, *col2, *col5;
int col3, col4;
} mydata_t;
/* simple implementation of strdup - in the event you don't have it */
char *mystrdup (const char *s)
{
if (!s) /* validate s not NULL */
return NULL;
size_t len = strlen (s); /* get length */
char *sdup = malloc (len + 1); /* allocate length + 1 */
if (!sdup) /* validate */
return NULL;
return memcpy (sdup, s, len + 1); /* pointer to copied string */
}
/* simple function to free all data when done */
void freemydata (mydata_t *data, size_t n)
{
for (size_t i = 0; i < n; i++) { /* free allocated strings */
free (data[i].col1);
free (data[i].col2);
free (data[i].col5);
}
free (data); /* free structs */
}
int main (int argc, char **argv) {
char buf[MAXC];
size_t arrsz = ARRSZ, line = 0, row = 0;
mydata_t *data = NULL;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* allocate an 'arrsz' initial number of struct */
if (!(data = malloc (arrsz * sizeof *data))) {
perror ("malloc-data");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line from file */
char c1[MAXC], c2[MAXC], c5[MAXC]; /* temp strings for c1,2,5 */
int c3, c4; /* temp ints for c3,4 */
size_t len = strlen (buf); /* length for validation */
line++; /* increment line count */
/* validate line fit in buffer */
if (len && buf[len-1] != '\n' && len == MAXC - 1) {
fprintf (stderr, "error: line %zu exceeds MAXC chars.\n", line);
return 1;
}
if (row == arrsz) { /* check if all pointers used */
void *tmp = realloc (data, arrsz * 2 * sizeof *data);
if (!tmp) { /* validate realloc succeeded */
perror ("realloc-data");
break; /* break, don't exit, data still valid */
}
data = tmp; /* assign realloc'ed block to data */
arrsz *= 2; /* update arrsz to reflect new allocation */
}
/* parse buf into fields, handle error on invalid format of line */
if (sscanf (buf, "%1023s %1023s %d %d %1023s",
c1, c2, &c3, &c4, c5) != 5) {
fprintf (stderr, "error: invalid format line %zu\n", line);
continue; /* get next line */
}
/* allocate copy strings, assign allocated blocks to pointers */
if (!(data[row].col1 = mystrdup (c1))) { /* validate copy of c1 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
if (!(data[row].col2 = mystrdup (c2))) { /* validate copy of c2 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
data[row].col3 = c3; /* assign integer values */
data[row].col4 = c4;
if (!(data[row].col5 = mystrdup (c5))) { /* validate copy of c5 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
row++; /* increment number of row pointers used */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
puts ("values stored in struct\n");
for (size_t i = 0; i < row; i++)
printf ("%-4s %-10s %4d %4d %s\n", data[i].col1, data[i].col2,
data[i].col3, data[i].col4, data[i].col5);
freemydata (data, row);
return 0;
}
Сейчас тестируем.
Пример входного файла
$ cat dat/fivefields.txt
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
Пример использования / Вывод
$ ./bin/fgets_fields <dat/fivefields.txt
values stored in struct
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока памяти, таким образом, (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/fgets_fields <dat/fivefields.txt
==1721== Memcheck, a memory error detector
==1721== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1721== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==1721== Command: ./bin/fgets_fields
==1721==
values stored in struct
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
==1721==
==1721== HEAP SUMMARY:
==1721== in use at exit: 0 bytes in 0 blocks
==1721== total heap usage: 11 allocs, 11 frees, 243 bytes allocated
==1721==
==1721== All heap blocks were freed -- no leaks are possible
==1721==
==1721== For counts of detected and suppressed errors, rerun with: -v
==1721== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.