Thread: C Primer Plus - Chapter 14 - Programming Exercise 6 strangeness!

  1. #1
    Registered User
    Join Date
    Apr 2019
    Posts
    121

    C Primer Plus - Chapter 14 - Programming Exercise 6 strangeness!

    I decided to go over the C Primer Plus (6th) again to see if I get a better understanding.

    I have seen a few things which I was not aware of. It's been kind of nice.

    But when doing the exercise listed above, I noticed something strange. I had the struct you are supposed to display, printed from the `main()` function. It worked perfectly. I wanted to clean up the `main()` function and put the display struct code into a `print_roster` function.

    For some reason the `print_roster()` function doesn't work right. I really can't understand it. Here is the code:

    Code:
    #include <ctype.h>
    #include <errno.h>
    #include <libgen.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define PROGRAM_NAME_SIZE   50
    #define TOTAL_PLAYRERS      19
    
    struct _roster {
        int number;
        char first[20];
        char last[20];
        int bats;
        int hits;
        int walks;
        int rbis;
        float average;
    };
    
    char program_name[PROGRAM_NAME_SIZE + 1] = {0};
    
    void calculate_average (struct _roster *roster);
    void print_roster(struct _roster *roster);
    
    int main (int argc, char *argv[])
    {
        struct _roster roster[TOTAL_PLAYRERS] = {
            {0, "Joe", "Shmoe", 3, 1, 0, 0},
            {4, "Shoeless", "Joe", 5, 1, 1, 1},
            {5, "Joey", "Makrs", 4, 1, 1, 0},
            {7, "Slim", "Jim", 1, 0, 0, 0},
            {18, "Jay", "Hay", 9, 2, 2, 2},
        };
    
        strcpy(program_name, basename(argv[0]));
    
        calculate_average(roster);
    //    print_roster(roster);
    
    /*
        char temp[5] = {0};
    
        puts(" #  First      Last        B   H   W  RBI   AVG");
        puts("==  =====      ====       ==  ==  ==  ===   ===");
    
        for(int x = 0; x < TOTAL_PLAYRERS; x++)
            if(roster[x].first[0] != '\0')
            {
                sprintf(temp, "%.3f", roster[x].average);
                printf("%2d  %-10s %-10s %2d  %2d  %2d   %2d  %s\n", roster[x].number, roster[x].first, roster[x].last, roster[x].bats, roster[x].hits, roster[x].walks, roster[x].rbis, &temp[1]);
            }
    */
    
        exit(EXIT_SUCCESS);
    }
    
    void calculate_average (struct _roster *roster)
    {
        for(int x = 0; x < TOTAL_PLAYRERS; x++)
            if(roster[x].first[0] != '\0')
                roster[x].average = (float) roster[x].hits / (float) roster[x].bats;
    }
    
    void print_roster(struct _roster *roster)
    {
        char temp[5] = {0};
    
        puts(" #  First      Last        B   H   W  RBI   AVG");
        puts("==  =====      ====       ==  ==  ==  ===   ===");
    
        for(int x = 0; x < TOTAL_PLAYRERS; x++)
            if(roster[x].first[0] != '\0')
            {
                sprintf(temp, "%.3f", roster[x].average);
                printf("%2d  %-10s %-10s %2d  %2d  %2d   %2d  %s\n", roster[x].number, roster[x].first, roster[x].last, roster[x].bats, roster[x].hits, roster[x].walks, roster[x].rbis, &temp[1]);
            }
    }
    The program has been minimized a bit by removing the reading of the roster file to populate the struct array. Basically, it you run the program as is, it displays the correct info:

    Code:
     #  First      Last        B   H   W  RBI   AVG
    ==  =====      ====       ==  ==  ==  ===   ===
     0  Joe        Shmoe       3   1   0    0  .333
     4  Shoeless   Joe         5   1   1    1  .200
     5  Joey       Makrs       4   1   1    0  .250
     7  Slim       Jim         1   0   0    0  .000
    18  Jay        Hay         9   2   2    2  .222
    But if you comment out the display block of code, and uncomment the `print_roster(roster);` line, the program gets messed up and just keeps repeating the final line:

    Code:
     #  First      Last        B   H   W  RBI   AVG
    ==  =====      ====       ==  ==  ==  ===   ===
     0  Joe        Shmoe       3   1   0    0  .333
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
     0  Joe        Shmoe       3   1   0    0  .200
    It's like the for loop in the function stops working. I can't explain it. Can someone here help me understand what is happening?

  2. #2
    Registered User
    Join Date
    Sep 2020
    Posts
    425
    Hi!

    You smash the stack here:

    Code:
                sprintf(temp, "%.3f", roster[x].average);

    Make this array bigger:
    Code:
        char temp[5] = {0};
    Or work out a format string that avoids the need to use "temp" at all... e.g. "%6.3f".

    And also consider replacing your strcpy() with strncpy(), to make that safe too.
    Last edited by hamster_nz; 10-25-2022 at 03:30 PM.

  3. #3
    Registered User
    Join Date
    Apr 2019
    Posts
    121

    Cool

    Quote Originally Posted by hamster_nz View Post
    You smash the stack here
    SOB! I don't know how I missed that. Ty for your eyes!

    But I don't see how it seems to works when in `main()`. I guess I will just have to swallow the pill of `undefined behaviour`.

    Ty for your assistance.

    Quote Originally Posted by hamster_nz View Post
    Or work out a format string that avoids the need to use "temp" at all... e.g. "%6.3f".
    For the record, the whole purpose of moving the float calculation to a string was to remove the leading zero from a float number (as batting averages don't usually list the leading 0 of a decimal point).
    Last edited by Yonut; 10-25-2022 at 03:48 PM. Reason: Adding more explaination.

  4. #4
    Registered User
    Join Date
    Sep 2020
    Posts
    425
    Maybe:
    Code:
    int avg = average*1000;
    if(avg > 999) avg = 999;
    if(avg < 0) avg = 0;
    printf("Average is .%03d\n", avg);

  5. #5
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    Quote Originally Posted by hamster_nz View Post
    Maybe:
    Code:
    int avg = average*1000;
    if(avg > 999) avg = 999;
    if(avg < 0) avg = 0;
    printf("Average is .%03d\n", avg);
    Nice, I was going to try to make it an int and format it in printf, but I was thinking if I multiplied it by 1000, that it would strip off all leading zeros. So I stopped and did it that way.

    I like yours better, because I think I can just do the conversion in the printf statement. As nobody hits 1000 and it can't be lower than 0.

    Code:
    printf("Average is .%03d\n", (int) (avg * 1000));
    Thanks again.

  6. #6
    Registered User
    Join Date
    Apr 2021
    Posts
    140
    I copied your program, made some changes, and it worked fine:

    1. I compiled with gcc -Wall -Wextra roster.c and addressed all the diagnostics that gcc put out.

    2. I added a 0.0 initializer for the average field in the struct initializers.

    3. I changed the declaration of the temp array to 15 characters.

    4. I added (void)argc; (void)argv; to main, to suppress the unused-argument warning (I assume you are actually using those parameters in your read-in-the-data-from-a-file variant of this program.

    With those changes made, I got:

    # First Last B H W RBI AVG
    == ===== ==== == == == === ===
    0 Joe Shmoe 3 1 0 0 .333
    4 Shoeless Joe 5 1 1 1 .200
    5 Joey Makrs 4 1 1 0 .250
    7 Slim Jim 1 0 0 0 .000
    18 Jay Hay 9 2 2 2 .222
    Then I went back, changed the declaration of temp to just 5 characters again, and got results the same as yours.

    Obviously, the size of the temp buffer makes a difference. Consider:

    temp = [_, _, _, _, _];

    sprintf("%0.3f", 0.123); // temp = [0, ., 1, 2, 3, NUL]; <-- 6 bytes!

    Where does the 6th byte go? It goes into the next location on the stack!

    What is stored at the next location on the stack? I have no idea!

    Except that I do have an idea: it's probably the x variable!

    Most, if not all, Intel CPU's are "little-endian". That is, they store multi-byte numbers with the least-significant bits in the lowest-addressed byte. The number 0x11223344 would be stored as bytes 0x44, 0x33, 0x22, 0x11 in that order.

    More importantly, the number 0x00000001 would be stored as 0x01, 0x00, 0x00, 0x00.

    And if something, like, say, sprintf, came along and wrote a 0x00 to the very first byte, it would replace the 0x01 and convert x from 0x00000001 back into 0x00000000.

    So what might happen?

    x = 0
    sprintf writes the average to temp, plus a 0-byte to x, replacing 0x00000000 with 0x00000000 (no change).
    the roster[x]'th player is printed out, with average = temp (0.333)
    x += 1
    sprintf writes the average to temp, plus a 0-byte to x, replacing 0x00000001 with 0x00000000 (1 -> 0)
    x = 0 because of this
    the roster[x]'th player is printed out, with average = temp (0.200)
    x += 1 (again)
    ... etc ...

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. C Primer Plus Exercise ch7.08
    By llnnttss in forum C Programming
    Replies: 7
    Last Post: 05-18-2016, 07:06 AM
  2. C Primer Plus Exercise ch7.02
    By llnnttss in forum C Programming
    Replies: 3
    Last Post: 05-15-2016, 04:36 PM
  3. c++ primer exercise 16.12
    By frank67 in forum C++ Programming
    Replies: 18
    Last Post: 09-30-2015, 03:32 PM
  4. c++ primer exercise 15.31
    By frank67 in forum C++ Programming
    Replies: 10
    Last Post: 09-23-2015, 09:38 AM
  5. c++ primer exercise 15.26
    By frank67 in forum C++ Programming
    Replies: 2
    Last Post: 09-18-2015, 09:34 AM

Tags for this Thread