Поскольку из комментариев вы знаете, что ваше объявление ваших символьных массивов страдает от символа one-too-small , по крайней мере, в случае char state_code[2];
, оставляющего ваш массив без nul- завершающий символ , ведущий к Undefined Behavior , вы должны убедиться, что у вас есть допустимое хранилище для всего вашего ввода. (не экономьте на размере буфера)
В общем, вы усложняете себе жизнь немного сложнее, чем это должно быть. Вместо того, чтобы пытаться использовать strtok()
и поле подсчета и обрабатывать каждое поле в цепочке из 8 частей if, else if ...
, у вас есть фиксированные поля ввода, поэтому просто проанализируйте данные с помощью sscanf()
и проверьте количество преобразований, чтобы подтвердить успешный синтаксический анализ, например,
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,\n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
Здесь list_t
- это просто дополнительная структура-оболочка, которая содержит указатели head
и tail
для вашего связанного списка. Это позволяет вам объявлять несколько списков в необходимой области и позволяет вставлять одну и ту же O (1), поскольку указатель tail
всегда указывает на последний узел в вашем списке (ваш temp
). Здесь head
и tail
являются просто частью оболочки и передаются как параметр, вместо того, чтобы объявлять указатель на список как глобальный (плохая практика). Каждый из узлов в вашем списке и структура-оболочка могут быть записаны как:
#define BYTE8 8 /* if you need a constant, #define one (or more) */
#define BYTE32 32
#define BYTE64 64
#define MAXC 1024
typedef struct node_t { /* list node */
/* 6 characters=date & 6 characters=time & counter no & city code */
long long int c_id;
/*Compulsory Fields (Cannot be Skipped)*/
char name[BYTE64];
long long int ph_no; //10 digit phone number
/*Non Compulsory Fields (Can be Skipped)*/
char address[BYTE64];
char city[BYTE32];
char state_code[BYTE8];
char pin[BYTE8];
char email[BYTE64];
struct node_t *next;
} node_t;
typedef struct { /* list wrapper with head & tail pointers */
node_t *head, *tail;
} list_t;
Тогда вместо того, чтобы писать load()
, содержащий как FILE
, так и операции со списком, храните операции со списком отдельно. Просто создайте функцию add()
, чтобы добавить узел в ваш список, например,
/** add node at end of list, update tail to end */
node_t *add (list_t *l, node_t *data)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate allocation */
perror ("malloc-node");
return NULL;
}
*node = *data; /* initialize members values */
if (!l->head) /* if 1st node, node is head/tail */
l->head = l->tail = node;
else { /* otherwise */
l->tail->next = node; /* add at end, update tail pointer */
l->tail = node;
}
return node; /* return new node */
}
Теперь вашей функции загрузки нужно только прочитать каждую строку из файла и проанализировать строку перед вызовом add()
, передав указателя на структура данных вместе с указателем списка в качестве параметров. Ваша функция load()
сокращается до:
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,\n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
( примечание: при использовании strtok()
или sscanf()
, нет необходимости удалять конечный '\n'
из вашего ввода строка - просто включите это как разделитель для strtok()
или исключите его из преобразования с помощью sscanf()
)
Кроме того, вам не нужно несколько вызовов puts()
и printf()
для печати каждого значение узла данных в вашем списке. Посмотрите, сколько вызовов функций вы делаете для печати данных. Вам нужен только ОДИН вызов printf()
, например
/** print all nodes in list */
void prn_list (list_t *l)
{
if (!l->head) {
puts ("list-empty");
return;
}
for (node_t *n = l->head; n; n = n->next)
printf ("\nCustomer ID: %lld\n"
"Name: %s\n"
"Phone Number: %lld\n"
"Address: %s\n"
"City: %s\n"
"State Code: %s\n"
"PIN: %s\n"
"Email: %s\n", n->c_id, n->name, n->ph_no, n->address, n->city,
n->state_code, n->pin, n->email);
}
В main()
просто объявите экземпляр вашей list_t
оболочки, откройте / подтвердите свой FILE
, а затем передайте указатель на список и поток файлов на ваш list_from_csv()
(ваш load()
), а затем распечатайте список и, наконец, освободите всю выделенную вами память, и все готово. (да, память будет освобождена при выходе, но выработайте полезные привычки на ранней стадии - совсем скоро вы начнете использовать функцию выделения памяти в функции, когда неспособность освободить до return
приводит к утечке памяти )
int main (int argc, char **argv) {
list_t list = { .head = NULL, .tail = 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;
}
if (!list_from_csv (&list, fp))
return 1;
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (&list);
del_list (&list);
}
В совокупности у вас будет что-то похожее на следующее:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BYTE8 8 /* if you need a constant, #define one (or more) */
#define BYTE32 32
#define BYTE64 64
#define MAXC 1024
typedef struct node_t { /* list node */
/* 6 characters=date & 6 characters=time & counter no & city code */
long long int c_id;
/*Compulsory Fields (Cannot be Skipped)*/
char name[BYTE64];
long long int ph_no; //10 digit phone number
/*Non Compulsory Fields (Can be Skipped)*/
char address[BYTE64];
char city[BYTE32];
char state_code[BYTE8];
char pin[BYTE8];
char email[BYTE64];
struct node_t *next;
} node_t;
typedef struct { /* list wrapper with head & tail pointers */
node_t *head, *tail;
} list_t;
/** add node at end of list, update tail to end */
node_t *add (list_t *l, node_t *data)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate allocation */
perror ("malloc-node");
return NULL;
}
*node = *data; /* initialize members values */
if (!l->head) /* if 1st node, node is head/tail */
l->head = l->tail = node;
else { /* otherwise */
l->tail->next = node; /* add at end, update tail pointer */
l->tail = node;
}
return node; /* return new node */
}
/** print all nodes in list */
void prn_list (list_t *l)
{
if (!l->head) {
puts ("list-empty");
return;
}
for (node_t *n = l->head; n; n = n->next)
printf ("\nCustomer ID: %lld\n"
"Name: %s\n"
"Phone Number: %lld\n"
"Address: %s\n"
"City: %s\n"
"State Code: %s\n"
"PIN: %s\n"
"Email: %s\n", n->c_id, n->name, n->ph_no, n->address, n->city,
n->state_code, n->pin, n->email);
}
/** delete all nodes in list */
void del_list (list_t *l)
{
node_t *n = l->head;
while (n) {
node_t *victim = n;
n = n->next;
free (victim);
}
}
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,\n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
int main (int argc, char **argv) {
list_t list = { .head = NULL, .tail = 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;
}
if (!list_from_csv (&list, fp))
return 1;
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (&list);
del_list (&list);
}
Пример использования / вывода
С вашим входным файлом в dat/customer_list.txt
, запустив программу, вы получите:
$ ./bin/customer_list dat/customer_list.txt
Customer ID: 1403201156540201
Name: Katherine_Hamilton
Phone Number: 2226179183
Address: 87_Thompson_St.
City: Fremont
State Code: IA
PIN: 502645
Email: k_hamilton@gmail.com
Customer ID: 2204201532220103
Name: Marc_Knight
Phone Number: 8023631298
Address: -
City: -
State Code: -
PIN: -
Email: -
Customer ID: 305201423120305
Name: Albie_Rowland
Phone Number: 8025595163
Address: -
City: Hamburg
State Code: NY
PIN: 140752
Email: -
Customer ID: 607201232220901
Name: Grant_Phelps
Phone Number: 4055218053
Address: -
City: -
State Code: -
PIN: -
Email: -
Использование памяти / Проверка ошибок
В любом коде, который вы пишете динамически выделяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохранять указатель на начальный адрес для блока памяти, поэтому (2) это может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного вами заблокировать, попытаться прочитать или b условный переход к неинициализированному значению и, наконец, подтверждение того, что вы освободили всю выделенную память.
Для Linux valgrind
- это нормальный выбор. Подобные средства проверки памяти существуют для каждой платформы. Все они просты в использовании, просто запустите свою программу через нее.
$ valgrind ./bin/customer_list dat/customer_list.txt
==14823== Memcheck, a memory error detector
==14823== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14823== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==14823== Command: ./bin/customer_list dat/customer_list.txt
==14823==
Customer ID: 1403201156540201
Name: Katherine_Hamilton
Phone Number: 2226179183
Address: 87_Thompson_St.
City: Fremont
State Code: IA
PIN: 502645
Email: k_hamilton@gmail.com
<snipped rest>
==14823==
==14823== HEAP SUMMARY:
==14823== in use at exit: 0 bytes in 0 blocks
==14823== total heap usage: 7 allocs, 7 frees, 6,728 bytes allocated
==14823==
==14823== All heap blocks were freed -- no leaks are possible
==14823==
==14823== For counts of detected and suppressed errors, rerun with: -v
==14823== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Хотя вы можете вылечить значительную часть своей проблемы, просто обеспечив достаточное хранилище для каждой строки, найдите время, чтобы подумать о приближении к синтаксическому анализу значений с помощью sscanf()
, и, поскольку вы контролируете преобразование, в этом нет необходимости чтобы удалить завершающий '\n'
из строки, прочитанной из файла. (просто не включайте новую строку в значение, анализируемое из вашей входной строки) Если вы действительно хотите проанализировать '\n'
с конца, вы должны использовать, например,
str[strcspn (str, "\n")] = 0;
Наконец, с обоими строка формата, используемая с sscanf()
и с strcspn()
выше, убедитесь, что вы точно понимаете, как они работают, см. man 3 scanf и man 3 strspn
Дайте мне знать, если у вас возникнут дополнительные вопросы.