Thread: While loop skipping first fgets - also reading text file into struct

  1. #1
    Registered User
    Join Date
    Dec 2013
    Posts
    11

    While loop skipping first fgets - also reading text file into struct

    Hello.
    I'm new to this board, also new to programming. This is the tail end of my first semester with C, and it hasn't gone so well. I've been lurking here from time to time desperately looking for tips and explanations, but wasn't ever confident enough to post my own question. So this is my first.

    I have searched, and have seen this topic addressed elsewhere on this board, but the answers were often over my head. Hopefully someone can dumb-it-down specifically for me.

    At any rate, any help would be wildly appreciated.

    My question:

    This is a project for class. i'm trying to build the start of a card game. (it's UNO if you're curious) I'm having quite a bit of trouble across the entire thing, but this one function is proving weirdly impossible for me - and it shouldn't be.

    The deck of my cards is set up as a struct:
    Code:
    struct cardz {
        char value[15];
        char color[7];
        int point_value;
    }card[108];
    One of the requirements for the project is a "Use of files".
    So I decided to create a text file with the card info called "new_deck".
    It looks like this:
    Code:
    Black Wild 50
    Black Wild 50
    Black Wild 50
    Black Wild 50
    Black Wild_Draw_Four 50
    Black Wild_Draw_Four 50
    Black Wild_Draw_Four 50
    Black Wild_Draw_Four 50
    Blue 0 0
    Blue 1 1
    Blue 1 1
    Blue 2 2
    Blue 2 2
    Blue 3 3
    
    
    ( and so forth...)
    The file shows the card color then a space, then the face value of the card, then a space, then the point value for the card all on each line.
    There are 108 cards - therefore the file has 108 lines.

    My current problem comes up with a function i'm trying to write that will read the text file, and place the appropriate values into the struct.
    I want the loop to stop when it gets to the end of the text file, so instead of doing a "for" loop (which i'm much more comfortable with) like this:
    Code:
     int i;
        for (i = 0; i < 108; i++) { 
            fgets (line, sizeof(line), f_in);
            line[strlen (line)-1] = '\0';
            sscanf (line, "%s %s %d", card[i].color, card[i].value, &card[i].point_value);
            }
    - i'm trying this "while" loop which is stressing me out because I can't seem to get it to work properly.
    Here's the whole function:
    Code:
    FILE *f_in;
    void getcards() {
        int i = 0;
        f_in = fopen ("new_deck", "r");
        if (f_in == NULL) {
            printf ("Cannot open file\n");
            exit (1);
        }
    while (fgets (line, sizeof(line), f_in) != NULL) {
            line[strlen (line)-1] = '\0';
            fscanf(f_in, "%s %s %d", card[i].color, card[i].value, &card[i].point_value); 
            i++;
        }
        fclose (f_in);
    }
    This function skips the first line in my text file.
    At least, i think that's what is happening.
    It only records 3 of the "Black Wild 50" cards and card[107] doesn't appear to have any values assigned to it.

    So what is happening?
    It keeps skipping line one.
    I've tried fiddling with my code here and there,
    (i suppose that's a dead giveaway that i don't know what i'm doing)
    but i get everything from skipping line one to skipping the first 2 lines, to only reading line 1 and skipping everything else... and oh yes. The occasional segmentation fault.

    Gah! Any help would be greatly appreciated.
    All i ask is that you keep it simple - because evidently, i'm an idiot.

  2. #2
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    In your first version, you use fgets followed by sscanf. However, in your second version you start out by doing fgets (which reads a line) and then fscanf (which continues reading AFTER the line you just read).

    In my opinion you should use the first approach that you wrote (fgets+sscanf). It means fgets is the only function that is reading from your file handle.

  3. #3
    Registered User
    Join Date
    Dec 2013
    Posts
    11
    Quote Originally Posted by c99tutorial View Post
    In my opinion you should use the first approach that you wrote (fgets+sscanf). It means fgets is the only function that is reading from your file handle.
    Thank you. I had considered that...
    But the following loop is one of my many attempts at code fiddling that gives me a segmentation fault - and I can't figure out why.

    Code:
        while (fgets (line, sizeof(line), f_in) != NULL) {
            line[strlen (line)-1] = '\0';
            sscanf (line, "%s %s %d", card[i].color, card[i].value, &card[i].point_value);
            i++;
        }
    Is this basically what you meant? If so, where'd I go wrong?

  4. #4
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    Quote Originally Posted by unsolicited View Post
    Thank you. I had considered that...
    But the following loop is one of my many attempts at code fiddling that gives me a segmentation fault - and I can't figure out why.

    Code:
             sscanf (line, "%s %s %d", card[i].color, card[i].value, &card[i].point_value);
    Do your members card[i].color, card[i].value actually have enough room to store the strings? One way to make sure is to make those members at least the same size as line.

  5. #5
    Registered User hk_mp5kpdw's Avatar
    Join Date
    Jan 2002
    Location
    Northern Virginia/Washington DC Metropolitan Area
    Posts
    3,817
    Code:
    line[strlen (line)-1] = '\0';
    If a line read from the file happens to be exactly the size of line (minus 1 for the trailing null), then the newline is not stored and this code will instead overwrite a legitimate character. For example, if your array is 10 characters long and you read in something that is exactly 9 characters (the 10th is the newline), your array at index 8 (index 9 is the null) would be the last character before the newline. This is probably not something you want to overwrite. You want to check first that this character is the newline character before you nullify it. Don't assume it will always be the newline.

    Are you sure that the file has the proper number of lines - 108 - and not any more? If this is not the case you currently have no bounds checking on the indexing variable i that would prevent more lines being processed and stored into an array that cannot handle the data. Maybe add an && i < 108 check to the while loop's condition.

    The sscanf function returns a value, the number of arguments that were successfully read (should be 3 in your case). Maybe add a check that this value is as expected and print a message if the value is anything other than 3.

    Are you certain that none of your strings in the file for color/value are larger than what is stated in your struct, 6 and 14 characters (and + 1 for the null)?
    "Owners of dogs will have noticed that, if you provide them with food and water and shelter and affection, they will think you are god. Whereas owners of cats are compelled to realize that, if you provide them with food and water and shelter and affection, they draw the conclusion that they are gods."
    -Christopher Hitchens

  6. #6
    Registered User
    Join Date
    Dec 2013
    Posts
    11
    Quote Originally Posted by hk_mp5kpdw View Post
    If a line read from the file happens to be exactly the size of line (minus 1 for the trailing null), then the newline is not stored and this code will instead overwrite a legitimate character.
    I've set my line to [255].
    My longest line in my file is 23 characters.

    Quote Originally Posted by c99tutorial View Post
    Do your members card[i].color, card[i].value actually have enough room to store the strings? One way to make sure is to make those members at least the same size as line.
    Yes.
    From my file:
    The longest string in "value" is 14 (so in my struct i've set char value[15])
    The longest string in "color" is 6 (so in my struct i've set char color[7])
    But as per your advice i set each of those to [255] and tried again... no change.



    Quote Originally Posted by hk_mp5kpdw View Post
    Code:
    line[strlen (line)-1] = '\0';
    You want to check first that this character is the newline character before you nullify it.
    I'm not clear on how to do that check, but I did try blowing away that entire line and it had no effect.

    I should make it clear that if I use a "for" loop
    Code:
     int i;
        for (i = 0; i < 108; i++) { 
            fgets (line, sizeof(line), f_in);
            line[strlen (line)-1] = '\0';
            sscanf (line, "%s %s %d", card[i].color, card[i].value, &card[i].point_value);
            }
    that runs until "i" hits "107" it works no problem. All the values are loaded just fine. String lengths, array sizes... everything works.
    I was just trying to do it with a "while" loop that stops at the end of the file in the spirit of making this function less specific to this program.

    But if i replace the "for" loop (above) with this "while" loop, it skips the first line.
    Code:
    int i = 0;
    while (fgets (line, sizeof(line), f_in) != NULL) {
            line[strlen (line)-1] = '\0';
            fscanf(f_in, "%s %s %d", card[i].color, card[i].value, &card[i].point_value); 
            i++;
        }
    And if I use this "while" loop instead, I get a segmentation fault.
    Code:
    int i = 0;
    while (fgets (line, sizeof(line), f_in) != NULL) {
            line[strlen (line)-1] = '\0';
            sscanf (line, "%s %s %d", card[i].color, card[i].value, &card[i].point_value);
            i++;
        }
    Thank you so much for trying to help. i really appreciate it.
    Am i posting correctly? Is it better if i post the entire source code? i was trying to keep my post more concise.
    Last edited by unsolicited; 12-17-2013 at 02:37 PM.

  7. #7
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    Quote Originally Posted by unsolicited View Post
    Am i posting correctly? Is it better if i post the entire source code? i was trying to keep my post more concise.
    Concise is good. Sometimes the entire source helps. I took your code and ran it and here's what I got after a few small changes. It runs for me without trouble. Notice I added a few checks for errors. Not strictly necessary, but if something goes wrong it helps to know where it happens.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    #define error(LINE_NO, LINE_STR) error_("%s:%d: Error occured while processing input line %d:\n%s", \
        __FILE__, __LINE__, (LINE_NO), LINE_STR)
    
    static inline void error_(const char *fmt, const char *file, int line, int line_no, const char *line_str)
    {
        fprintf(stderr, fmt, file, line, line_no, line_str);
        exit(1);
    }
    
    struct cardz {
        char value[255];
        char color[255];
        int point_value;
    } card[108];
    
    const size_t card_sz = 108;
        
    int main()
    {
        void init_cardz();
        void get_cardz(FILE *f_in);
        void print_cardz();
        
        init_cardz();
        get_cardz(stdin);
        print_cardz();
    }
    
    void init_cardz()
    {
        for (size_t i = 0; i < card_sz; i++) {
            strncpy(card[i].value, "", 255);
            strncpy(card[i].color, "", 255);
            card[i].point_value = 0;
        }
    }
    
    void get_cardz(FILE *f_in) 
    {
        char line[255];
        for (size_t i = 0;
            strncpy(line, "", sizeof(line)),
            fgets (line, sizeof(line), f_in) != NULL;
            i++
        ) {
            // Check if too many lines
            if (i >= card_sz) {
                error(i+1, line);
            }
            // Check if line is too long
            if (line[sizeof(line)-1] != '\0') {
                error(i+1, line);
            }
            // Process line
            char dummy;
            int retval = sscanf (line, "%s %s %d %c", card[i].color, 
                card[i].value, 
                &card[i].point_value, 
                &dummy);
            // Check if wrong number of parameters
            if (retval != 3) {
                error(i+1, line);
            }
        }
    }
    
    void print_cardz()
    {
        bool card_empty(struct cardz card);
        
        for (size_t i = 0; i < card_sz; i++) {
            // stop at first empty card
            if (card_empty(card[i])) {
                break;
            }
            printf("Card %d: %s, %s, %d\n", i, card[i].value, card[i].color, card[i].point_value);
        }
    }
    
    bool card_empty(struct cardz card)
    {
        return strcmp(card.value, "") == 0 && 
            strcmp(card.color, "") == 0 && 
            card.point_value == 0;
    }

  8. #8
    Registered User
    Join Date
    Dec 2013
    Posts
    11
    Quote Originally Posted by c99tutorial View Post
    I took your code and ran it and here's what I got after a few small changes...Notice I added a few checks for errors.
    Shoot!
    Thank you for your help But this is way over my head.

    Like... what is innit_cardz doing?
    It's copying "nothing(?)" to the struct? Is that... what's that for?

    Also i can't figure out what's happening in the "for" loop.
    My understanding of "for" loops was that you do counting stuff in the parenthesis.
    Like:
    Code:
    for (*where "i" starts"*, *how big "i" can get*, *how to increment "i"*) {
          do_something;
    }
    Are you reading the file and strncpy'ing "nothing" inside the "for" loop parenthesis? How does that work? I thought you were supposed to "do things" inside the brackets?

    Also what are size_t and retval? Are those defined somewhere?
    And what %c gets sent to &dummy? My file only has 3 values per line. What's that 4th value? And what does dummy do?

    Damn. It looks like you did a lot of work.
    Thank you so much.
    i'm sorry i don't understand it.

    I guess I'm stuck with my "for" loop.

  9. #9
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    a for loop is just a while loop with syntactic sugar. Consider this

    Code:
    int i=0;
    while (i < 100) {
       do_something();
       i++;
    }
    The reason a for loop is preferred is to make all loop control together. Consider if do_something() is longer. Maybe you'll forget to increment i.

    Code:
    for (int i=0; i < 100; i++) {
        do_something();
    }
    As for init_cards in my example, it is to initialize each card to a known state. Maybe it's not necessary in this case because you allocated card on the stack, but in general you should initialize variables. For an array of structs I think it's most convenient to make a simple function to do this work. It's quite boring to type in a long list of initial values for each struct member, although this is also possible.

  10. #10
    Registered User
    Join Date
    Dec 2013
    Posts
    11
    Right, i get that "while" and "for" are similar, but i wanted to use "while" because I don't see how to tell the "for" loop where the end of my file is. The "for" loop's loop control can only start, check and increment "i" around inside those parenthesis, right?

    That's why i'm so confused by this "for" loop you used.
    Instead of:
    Code:
     for (int i = 0; i < 100; i++) {
         do_something1();
         do_something2();
    }
    It looks like you're doing:
    Code:
    for (i = 0; do_something1(), do_something2(); i++){
         do_something3();
    }
    I don't understand "strncpy(blah), fgets(blah) != NULL" inside the loop control instead of something like "i < 100".

  11. #11
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    Let's start with the simplest fgets loop possible that will also allow you to keep track of the line numbers:

    Code:
    for (int lineno = 0; fgets(line, sizeof(line), f_in) != NULL; lineno++) 
    {
        /* do something with line and lineno */
    }
    This will work but it has the problem for me that it makes checking for errors difficult. If the line is too long, then fgets will not put a '\0' as the last character in the buffer. However, if the line is too short, then the last character in the buffer is undefined. I would like to be able to do a simple test like this:

    Code:
    if last character in line is not '\0':
        print error message "buffer overflow"
        end program
    To do this you can initialize "line" beforehand. One way is to use strncpy. If you look at the reference page for strncpy, the behaviour states that the entire buffer will be initialized with '\0' if you pass it an empty string. So:

    Code:
    for (int i = 0;
            strncpy(line, "", sizeof(line)),
            fgets(line, sizeof(line), f_in) != NULL;
            i++
        )
    {
        /* do something with line and lineno */
    }
    This is the same except now it is possible to tell whether the line overflows the buffer or not, by checking line[sizeof(line)-1] and seeing if this is '\0'. If it is not '\0' then it means fgets ran out of room.

    The exact meaning of , in this instance is

    expr1(), expr2(), expr3(), ..., exprn()

    it means each expr1, ..., exprn will be evaluated, but only the last expr's return value is used. In the case of a while loop, it means that exprn() will be the one that decides whether the loop continues or not. The previous expressions will be evaluated but are not used for loop control purposes.
    Last edited by c99tutorial; 12-17-2013 at 06:22 PM.

  12. #12
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Quote Originally Posted by c99tutorial View Post
    This will work but it has the problem for me that it makes checking for errors difficult. If the line is too long, then fgets will not put a '\0' as the last character in the buffer. However, if the line is too short, then the last character in the buffer is undefined. I would like to be able to do a simple test like this:
    Any reason you don't use memset to initialize line to all \0 chars? It's far clearer and more intuitive as to what you're doing. Or, if it's really just the last char you're concerned with: line[sizeof(line) - 1] = '\0'.

    But that's all moot here, since fgets is guaranteed to null-terminate the buffer: fgets(3): input of char/strings - Linux man page.

  13. #13
    Registered User
    Join Date
    Dec 2013
    Posts
    11
    AAAAAAAAAAAAAAAGH AAAAAAAAAAAUUUUUUUGH GAAAAAAAAH!

    IT WORKED! IT WORKED! AAAAAAAAAAAAH IT WORKS!

    Thank you, c99tutorial.
    That explanation was awesome.

    I tried using that simple "for" loop, and for the first time in 2 days, it WORKED!

    So, just to get this straight...
    We're actually reading the file, and writing to my line array from inside the loop control?
    I thought the loop control stuff inside the parentheses could only evaluate whether to stop or continue the loop. But we're actually doing work inside there. That seems like cheating - by that i mean, it's counter to the way I was thinking about loop control. i think that's why i was so paralyzed with confusion.

  14. #14
    Registered User
    Join Date
    Nov 2012
    Posts
    1,393
    anduril462 is right. Let's just simplify it to this:

    Code:
    for (int i = 0; fgets (line, sizeof(line), f_in) != NULL; i++) {
    		// Check if too many lines
    		if (i >= D) {
    			exit(1);
    		}
    		// Check if line is too long
    		if (strlen(line) == sizeof(line)-1) {
    			exit(2);
    		}
    		// Process line
    		/* ... */
    	}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Don't know if this is a struct, a loop, or an fgets issue.
    By figarojones in forum C Programming
    Replies: 2
    Last Post: 11-15-2012, 01:54 PM
  2. Reading data from a text file into a C struct
    By someone2088 in forum C Programming
    Replies: 11
    Last Post: 12-07-2011, 10:14 AM
  3. reading text file to struct help needed!
    By werdy666 in forum C++ Programming
    Replies: 2
    Last Post: 01-25-2009, 11:37 AM
  4. Skipping Columns in Text File
    By Vulpecula in forum C++ Programming
    Replies: 3
    Last Post: 08-08-2008, 12:10 PM
  5. skipping \n when reading from file
    By Spedge in forum C Programming
    Replies: 3
    Last Post: 08-26-2003, 08:05 AM

Tags for this Thread