0
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
int CurrentCnt = 0;

#define MAX_ID_LEN 30
#define MAX_NAME_LEN 30
#define MAX_PRICE_LEN 30
#define MAX_DISCOUNT_LEN 30
typedef struct {
    char    goods_id[MAX_ID_LEN];
    char    goods_name[MAX_NAME_LEN];
    int     goods_price;
    char    goods_discount[MAX_DISCOUNT_LEN];
    int     goods_amount;
    int     goods_remain;
} GoodsInfo;

//--------------------------------------------------------------------
//define node
//--------------------------------------------------------------------
typedef struct node
{
    GoodsInfo data;
    struct node *next;
} GoodsList;

bool check_nullfile(void)
{
    FILE *fp = fopen("goodsinfo.txt", "r");
    //file not exist
    if (!fp) {
        printf("no files found.\n");
        FILE *fp = fopen("goodsinfo.txt", "w");
        fclose(fp);

        return false;
    }
        //file already exist
    else {
        int temp;
        //res for try to read file if file null feof() can't determine             
whether file is null or not
        int res = fscanf(fp, "%d", &temp);
        fclose(fp);
        if (res <= 0)
            return false;
        else
            return true;
    }
}

void info_init(GoodsList **L) {

    if(check_nullfile())
    {
        FILE * fp;
        fp=fopen("goodsinfo.txt", "r");
        GoodsList *listptr;
        while (1)
        {
            if (feof(fp)) break;
            listptr=(GoodsList*)malloc(sizeof(GoodsList));
            listptr->next=(*L)->next;
            (*L)->next=listptr;
            fscanf(fp,"%4s\t",listptr->data.goods_id);
            fscanf(fp,"%4s\t",listptr->data.goods_name);
            fscanf(fp,"%d\t",&(listptr->data.goods_price));
            fscanf(fp,"%s\t",listptr->data.goods_discount);
            fscanf(fp,"%d\t",&(listptr->data.goods_amount));
            fscanf(fp,"%d",&(listptr->data.goods_remain));
/*          printf("%c%c%c%c\n",listptr->data.goods_id[0],listptr-                
>data.goods_id[1],listptr->data.goods_id[2],listptr->data.goods_id[3]);
            printf("%c%c%c%c\n",listptr->data.goods_name[0],listptr-        
>data.goods_name[1],listptr->data.goods_name[2],listptr- 
>data.goods_name[3]);
            printf("%d\n",listptr->data.goods_price);
            printf("%c%c%c%c\n",listptr->data.goods_discount[0],listptr- 
>data.goods_discount[1],listptr->data.goods_discount[2],listptr- 
>data.goods_discount[3]);
            printf("%d\n",listptr->data.goods_amount);
            printf("%d\n",listptr->data.goods_remain); these are my 
testing*/
            CurrentCnt++;
            if (feof(fp)) break;
        }
        fclose(fp);
    }

    printf("%d\n", CurrentCnt);
}

int main (void)
{
    GoodsList **L;
    L=(GoodsList**)malloc(sizeof(GoodsList*));
    info_init(L);
    return 0;
}

I have a testing file which includes five groups of files. When I run this program, the fifth group of data can't be output correctly. My testing data is

1000    new1    90  0.9 90  80
1001    new2    80  0.9 80  80
1002    new3    70  0.8 10  10
1003    new4    88  0.8 70  80
1004    new5    100 0.8 70  80

Why the position4 works but others can't?Position1 2 3will make CurrentCnt become 6 but 5.In the last loop the program get nothing but why doesn't it jump out the loop? My new poor program:

void info_init(GoodsList **L) {

if(check_nullfile())
{
    FILE * fp;
    fp=fopen("goodsinfo.txt", "r");
    GoodsList *listptr;
    while (1/*feof(fp) position1*/)
    {
        //if (feof(fp)) break; //position2
        listptr=malloc(sizeof(*listptr));
        listptr->next=*L;
        *L=listptr;
        //if (feof(fp)) break;//position3
        fscanf(fp,"%s\t",listptr->data.goods_id);
        fscanf(fp,"%s\t",listptr->data.goods_name);
        fscanf(fp,"%d\t",&(listptr->data.goods_price));
        fscanf(fp,"%s\t",listptr->data.goods_discount);
        fscanf(fp,"%d\t",&(listptr->data.goods_amount));
        fscanf(fp,"%d",&(listptr->data.goods_remain));
        //if (feof(fp)) break;//position4
        CurrentCnt++;
    }
    fclose(fp);
}

printf("%d\n", CurrentCnt);

}

tetttest
  • 3
  • 3
  • [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Yu Hao Dec 20 '18 at 15:27
  • 2
    The `if (feof(fp)) break;` is misplaced. It must be immediately after the first read. Please read this [FAQ](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong). – Serge Ballesta Dec 20 '18 at 15:33
  • Please check my updating program.Thank you. – tetttest Dec 21 '18 at 01:44
  • `if (feof(fp))` is not just misplaced, it's totally wrong. On a read error, this is an infinite loop. Check the value returned by scanf. Better yet, don't use scanf at all. "is something wrong with fscanf?" YES. It's a convoluted API that has lead to countless programming errors. Use fread. – William Pursell Dec 21 '18 at 12:14
  • There are many reasons that fopen can fail other than a file not existing. – William Pursell Dec 21 '18 at 12:15
  • http://c-faq.com/stdio/scanfprobs.html – William Pursell Dec 21 '18 at 12:39

2 Answers2

1

The way you have factored your code (1) to handle the list; and (2) to add data to the list, it so confused, and so lacking in validation that it is no wonder you are having difficulty sorting it out.

The read of the data is flawed from the beginning. See Why is while ( !feof (file) ) always wrong?. Further, you fail to validate a single return of fscanf. If one read fails, you invoke Undefined Behavior by blindly using the indeterminate value (and likely every value from that point forward will be indeterminate). All bets are over at that point.

However, you are to be commended for your use of #define to define constants you need, but you then fail to protect your array bounds by including field-width modifiers with all char* conversion specifiers. While you #define constants, you then turn around and hard-code your file name. Don't do that. Pass your filename as an argument to your program or prompt for its entry.

Whenever processing a "line-of-data" at a time, you should use a line-oriented input function such as fgets or POSIX getline and then parse the values you need from the line of data. This provides the benefit of allowing separate validations of the (1) read of the data from the file; and (2) the parse of the values from the resulting buffer. If for some reason there is an error in the format, your parse will fail, and you can simply continue the read loop and read the next line -- without the risk of Undefined Behavior.

When you are creating a list, all you need is a single append() function which will create the list if it does not exist, and allocate and add each additional node to the list as needed. Your code looks to attempt a simple forward-chaining for adding nodes to your list (which is fine, but without more will result in the list being held in memory in reverse order)

Don't mix reading data with list operations. Instead read and pass the data required to your append() function. While this largely up to you, failure to separate your read/parse and append will just lead to list functions at are not reusable.

For example, instead of trying to read and parse data within your list function, open your file in main() and parse the data to a temporary goodsinfo1 struct and pass the list address and pointer to your temporary data to your append function. You could do something similar to the following for your read and parse of data and passing the needed values to your function:

int main (int argc, char **argv)
{
    char buf[MAXC];             /* read buffer */
    size_t linecnt = 0;         /* line counter */
    goodslist *list = NULL;     /* linked list pointer */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {         /* read each line of data */
        goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
        /* parse and validate data in buf (can be separate function) */
        if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id, 
                    tmp.goods_name, &tmp.goods_price, tmp.goods_discount, 
                    &tmp.goods_amount, &tmp.goods_remain) != 6) {
            fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
            continue;
        }
        if (!append (&list, &tmp))      /* append to list/validate */
            break;
    }
    if (fp != stdin)    /* close file if not stding */
        fclose (fp);

    prn_list (list);    /* print list */
    free_list (list);   /* free list data */

    return 0;
}

(note: the program takes the filename to read data from as the first argument, or reads from stdin by default if no filename is provided. Also note, you declare your list as a pointer to goodslist, not a pointer-to-pointer-to goodslist)

With your data read and parsed, your append() function simply has to allocate storage for data and allocate storage for a new list node. It only has two cases to handle (1) is the list empty? -- leave node->next = NULL; otherwise (2) set node->next equal to the current list address before assigning the address for your new node as the new list address to chain your nodes together, e.g.

/* function to allocate goodslist node and append allocated goodsinfo 
 * data to list. Takes address of list pointer and pointer to goodsinfo data 
 * to append to list. Returns pointer new node on success, NULL otherwise.
 */
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
    goodsinfo *data = malloc (sizeof *data);    /* allocate/validate data */
    if (!data) {
        perror ("malloc-data");
        return NULL;
    }
    *data = *tmp;   /* fill allocated data block with tmp values */

    /* allocate/validate list node */
    goodslist *node = malloc (sizeof *node);
    if (!node) {
        perror ("malloc-node");
        free (data);
        return NULL;
    }
    node->data = data;  /* initialize data and set next NULL */
    node->next = NULL;

    if (*l) /* if list exists, chain next to list */
        node->next = *l;

    return ((*l = node)->data); /* assign new node as list, return data */
}

Putting it altogether you could do something like the following:

#include <stdio.h>
#include <stdlib.h>

#define MAX_ID_LEN          30
#define MAX_NAME_LEN        MAX_ID_LEN
#define MAX_PRICE_LEN       MAX_NAME_LEN
#define MAX_DISCOUNT_LEN    MAX_PRICE_LEN
#define MAXC                1024    /* read buffer size (don't skimp) */

typedef struct {
    char    goods_id[MAX_ID_LEN];
    char    goods_name[MAX_NAME_LEN];
    int     goods_price;
    char    goods_discount[MAX_DISCOUNT_LEN];
    int     goods_amount;
    int     goods_remain;
} goodsinfo;

typedef struct goodslist {
    goodsinfo *data;        /* make data a pointer and allocate */
    struct goodslist *next;
} goodslist;

/* bool check_nullfile(void)
 * (poor test, if first char not 0-9, test fails)
 */

/* function to allocate goodslist node and append allocated goodsinfo 
 * data to list. Takes address of list pointer and pointer to goodsinfo data 
 * to append to list. Returns pointer new node on success, NULL otherwise.
 */
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
    goodsinfo *data = malloc (sizeof *data);    /* allocate/validate data */
    if (!data) {
        perror ("malloc-data");
        return NULL;
    }
    *data = *tmp;   /* fill allocated data block with tmp values */

    /* allocate/validate list node */
    goodslist *node = malloc (sizeof *node);
    if (!node) {
        perror ("malloc-node");
        free (data);
        return NULL;
    }
    node->data = data;  /* initialize data and set next NULL */
    node->next = NULL;

    if (*l) /* if list exists, chain next to list */
        node->next = *l;

    return ((*l = node)->data); /* assign new node as list, return data */
}

/* simple print list function */
void prn_list (goodslist *l)
{
    if (!l)
        return;

    while (l) {
        printf (" %-8s %-8s %8d %-8s %8d %9d\n", l->data->goods_id, 
                l->data->goods_name, l->data->goods_price, 
                l->data->goods_discount, l->data->goods_amount, 
                l->data->goods_remain);
        l = l->next;
    }
}

/* simple free list function */
void free_list (goodslist *l)
{
    if (!l)
        return;

    goodslist *iter = l;

    while (iter) {
        goodslist *victim = iter;
        free (iter->data);
        iter = iter->next;
        free (victim);
    }
}

int main (int argc, char **argv)
{
    char buf[MAXC];             /* read buffer */
    size_t linecnt = 0;         /* line counter */
    goodslist *list = NULL;     /* linked list pointer */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {         /* read each line of data */
        goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
        /* parse and validate data in buf (can be separate function) */
        if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id, 
                    tmp.goods_name, &tmp.goods_price, tmp.goods_discount, 
                    &tmp.goods_amount, &tmp.goods_remain) != 6) {
            fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
            continue;
        }
        if (!append (&list, &tmp))      /* append to list/validate */
            break;
    }
    if (fp != stdin)    /* close file if not stding */
        fclose (fp);

    prn_list (list);    /* print list */
    free_list (list);   /* free list data */

    return 0;
}

(note: your bool check_nullfile(void) does more harm than good and will fail if the first non-whitespace character isn't a digit)

Example Use/Output

(note: when using chaining without keeping a "last" pointer results in the list nodes stored in reverse order)

$ ./bin/ll_goodslist dat/goodsinfo.txt
 1004     new5          100 0.8            70        80
 1003     new4           88 0.8            70        80
 1002     new3           70 0.8            10        10
 1001     new2           80 0.9            80        80
 1000     new1           90 0.9            90        80

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/ll_goodslist dat/goodsinfo.txt
==3493== Memcheck, a memory error detector
==3493== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3493== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3493== Command: ./bin/ll_goodslist dat/goodsinfo.txt
==3493==
 1004     new5          100 0.8            70        80
 1003     new4           88 0.8            70        80
 1002     new3           70 0.8            10        10
 1001     new2           80 0.9            80        80
 1000     new1           90 0.9            90        80
==3493==
==3493== HEAP SUMMARY:
==3493==     in use at exit: 0 bytes in 0 blocks
==3493==   total heap usage: 11 allocs, 11 frees, 1,152 bytes allocated
==3493==
==3493== All heap blocks were freed -- no leaks are possible
==3493==
==3493== For counts of detected and suppressed errors, rerun with: -v
==3493== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Look things over and let me know if you have further questions.

footnotes

  1. While not an error, C generally avoids the use of camelCase or MixedCase variable names in favor of all lower-case while reserving upper-case names for use with macros and constants. It is a matter of style -- so it is completely up to you, but failing to follow it can lead to the wrong first impression in some circles.
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you for your kindest help.It's a part of my homework so I have to create a linked list by myself.I think I have better understanding of that. Thank you again.Merry Christmas! – tetttest Dec 23 '18 at 14:31
0

There are two problems. The main one is that you're creating the linked list incorrectly.

For the first record you read, the value of *L is undefined - you've only created L. So accessing (*L)->next will cause your program to crash.

listptr->next=(*L)->next;

But this is due to you having a misunderstanding about what L is in the first place. Your info_init function is being passed a GoodList ** because it is using that argument to pass back the newly created list. You're not meant to pass in a variable of GoodList **, you're meant to pass in a pointer to a GoodList *. This variable should be initialised as NULL to start with.

int main (void)
{
    GoodsList *L=NULL;
    info_init(&L);
    return 0;
}

and then instead of

listptr->next=(*L)->next;
(*L)->next=listptr;

you have

listptr->next=*L;
*L=listptr;

This means that the newly created list node will point to the previous node which is stored in *L. As in main it is initially NULL, this means the first node will point next to NULL. And then *L will be updated to point to the first node. And then the next time around, the second node will point to the first etc...

The other issue isn't quite so bad - you'll always read one extra empty node, because you're extending the list and then trying to read something in. Your check for EOF is too early as it'll only register EOF after you've tried to read something in.

A more robust method of reading in each record is to read in each line using fgets and using sscanf to read each field. You can also remove the need for the check_nullfile function by checking the return value of fopen. If the file is empty, you'll have an empty list as nothing will be read or allocated.

You also don't need to cast the return value of malloc and it's safer to use sizeof(*listptr) to work out the amount of memory listptr needs as it'll work even if you change the type of listptr.

void info_init(GoodsList **L) {

    FILE * fp;
    fp=fopen("goodsinfo.txt", "r");
    if(fp)
    {
        char line[512];
        GoodsList *listptr;
        while (fgets(line,512,fp))
        {
            listptr=malloc(sizeof(*listptr));
            listptr->next=(*L);
            (*L)=listptr;
            sscanf(line,"%4s\t%4s\t%d\t%s\t%d\t%d",listptr->data.goods_id,
                listptr->data.goods_name,
                &(listptr->data.goods_price),
                listptr->data.goods_discount,
                &(listptr->data.goods_amount),
                &(listptr->data.goods_remain));
            CurrentCnt++;
        }
        fclose(fp);
    }

    printf("%d\n", CurrentCnt);
}
Chris Turner
  • 8,082
  • 1
  • 14
  • 18
  • Thank you for your help.But I still have some problems.First, I tried to run your program and output listptr->data.goods_remain. The result is 1000,1001...,just is same as the id.And the second is I try to edit my program and found something confusing.In my new program if I choose the position 4 then it works, but when I choose other positions then CurrenCnt will be 6 which shows the last loop don't receive anything but why feof() can't work? – tetttest Dec 21 '18 at 01:36
  • Apologies. I missed out one change - to merge the `scanf` calls in to one since the string will contain all the fields. – Chris Turner Dec 21 '18 at 10:19
  • `feof()` doesn't work because it doesn't know it has reached the end of the file until it tries reading something. So you'd have to put it after the first `fscanf` and then remove the newly created node – Chris Turner Dec 21 '18 at 10:21
  • Thank you for your help. I think now I have deeper understanding of that.Thanks for your kindest help. Merry Christmas. – tetttest Dec 23 '18 at 14:33