4

In the book Head First C, fist edition, there is this progam:

#include <stdio.h>
#include <string.h>

char tracks[][80] = {
    "I left my hearth in Harvard Med School",
    "Newark, NewarK - a wonderful town",
    "Dancing with a  Dork",
    "From here to maternity",
    "The girl from Iwo Jima",
};

void find_track(char search_for[]) {
    int i;

    for(i = 0; i < 5; i++) {
        if (strstr(tracks[i], search_for)) {
            printf("Track %i: '%s'\n", i, tracks[i]);
        }
    }
}

int main() {
    char search_for[80];
    printf("Search for: ");
    fgets(search_for, 80, stdin);
    find_track(search_for);
    return 0;
}

However, when compiled and tested, you don't get the expected results. After breaking my head for a bit, I decided to look for the documentation for fgets, I discovered that the function reads up to an including a newline character, which is why no matter what I search for, I never get the expected result. However, in the book they say the program works when tested. Is this a book error, or am I missing something?

PS. the error can be easily fixed using scanf, which becomes obvious once you know why the program doesn't work as expected.

PS2. I remember C++ has some syntax to ignore the newline character. Does C have something similar?

Buzu
  • 1,059
  • 2
  • 8
  • 15
  • 7
    Maybe the book was originally written to use `gets()` which did omit the newline (but see [Why the `gets()` function is too dangerous to use](http://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used)), and they didn't recheck. Yes, `fgets()` preserves the newline and since the code doesn't clean up (erase) the newline, it is broken, despite anything that they said. – Jonathan Leffler Mar 16 '16 at 22:56
  • Thanks guys. The book actaully says: "Even though fgets() is seen as a safer-to-use function than scanf(), the truth is that the older gets() function is far more dangerous than either of them. The reason? The gets() function has no limits at all:" So, I don't think they intended to use gets. – Buzu Mar 16 '16 at 22:59
  • 3
    Not using the `const` qualifier for arguments not changed makes one wonder about the quality of that book. – too honest for this site Mar 16 '16 at 23:03
  • I've found the book very informative, and easy to follow. Not being experienced in C, I can't defend the quality of the book, but being the 2.5 chapter of an introductory book, I can guess they don't want to overwhelm the reader. Thanks for the tip, however. It is very welcomed. – Buzu Mar 16 '16 at 23:08
  • 1
    [This program works](https://ideone.com/MIttBH) when the input doesn't contain any newline character. – MikeCAT Mar 16 '16 at 23:58
  • @MikeCAT, the function docs says it reads up to and including a newline character. From that, I understand that for any practical uses, the input has to have a newline character. Also, in the console, you have to press enter in order for the program to continue. Is there any way to let the program continue when using stdin without pressing enter? – Buzu Mar 17 '16 at 02:33
  • 1
    `echo -n "your input" | ./a.out` Where `echo` is a command line tool that print the command line arguments to stdout and supports `-n` option that mean not to print newline character after printing the arguments, and `a.out` is your compiled program (executable binary). – MikeCAT Mar 17 '16 at 02:36
  • Man, that smart lol... I find it funny, though. I see you sitting there thinking "This sh*t works because I say so". I would just upvote your comment to infinity, but I can't. However, I don't think this book expected people to do that, I think the code is faulty. Nice pipe usage there by the way. – Buzu Mar 17 '16 at 02:45

2 Answers2

4

I also encountered the error, but the newest version has corrected the error. In fact, the function 'char *fgets(char *s, int size, FILE *stream) works like what you said. It only read up to size-1. If it encounters a null character('\0'), it only add ('\0') to the buffer. So when we used the fuction find_track(), we can't get anything because of search_for has contained ('\0'). There are some solutions to handle it.

  • scanf("%79s", search_for). Attention, don't only use %s because you wil encounter the same situation like fgets().
  • scanf("%79[^\n]", search_for), it has some difference with the former one. The advantage that you can avoid the blank problem.

PS: The first one, you can add a new fuction to avoid the blank problem. Like this:

void remove_tail_newline(search_for)
{ size_t len = strlen(str);
   if(len > 0 &&str[len-1] =='\n') str[len -1] ='\0';
}
pacholik
  • 8,607
  • 9
  • 43
  • 55
Ghoster
  • 75
  • 10
3

You're right; this is indeed an error in the book.

I recommend against using scanf for user input. It is hard to use correctly, error detection is non-trivial (if it is possible at all), and error recovery is impossible.

All user input should be done by reading a line first, then analyzing it afterwards (e.g. using strtol or sscanf). Reading a line can be done with fgets, but unfortunately there's no syntax or feature for ignoring the \n.

What you can do instead is:

if (fgets(line, sizeof line, stdin)) {
    line[strcspn(line, "\n")] = '\0';  // remove trailing '\n', if any
    do_stuff_with(line);
}
melpomene
  • 84,125
  • 8
  • 85
  • 148