2
char tracks[][80] = {
    "I left my heart 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++) {
        char *pointer = strstr(tracks[i], search_for);
        printf("P:%s\n", pointer);
    } 
}

int main() {

    char search_for[80]; 
    printf("Search for: "); 
    fgets(search_for, 80, stdin); 
    // scanf("%79s", search_for);
    find_track(search_for);


    return 0;
}

That's a example for the usage of strstr() and fgets(). But when I run it, it cannot return the matched pointer, it's always the null.

Search for: wonderful
P:(null)
P:(null)
P:(null)
P:(null)
P:(null)

And I tried some different expressions: 1.I reduced the capacity of the search_for array from 80 to 10:

char search_for[10];

it works!

Search for: wonderful
P:(null)
P:wonderful town
P:(null)
P:(null)
P:(null)

2.I replaced the function fgets() by scanf().

scanf("%79s", search_for);

it also works!

Search for: wonderful
P:(null)
P:wonderful town
P:(null)
P:(null)
P:(null)

Therefore, I am confused about it, and anyone can tell me the deep reason about the phenomenon?

Lundin
  • 195,001
  • 40
  • 254
  • 396
Hao Wu
  • 111
  • 6
  • 7
    Welcome to Stack Overflow. Please read the [About] and [Ask] pages. Please do not post images — and don't have all your images described as 'enter image description here'. Please post the text in the question. Thank you! – Jonathan Leffler Jan 08 '20 at 03:50
  • You have edited out the 'enter image description here' which is good but not really what is required. We need the code as text in the question and not as images - [reasoning](https://meta.stackoverflow.com/questions/285551/why-not-upload-images-of-code-on-so-when-asking-a-question). – kaylum Jan 08 '20 at 03:54
  • 1
    Note that `fgets()` includes the newline when there's space. If the string is 80 bytes, there's enough space for `"wonderful\n"`; if the string is only 10 bytes, you can only store `"wonderful"` (9 letters and a null byte). The string you're searching in doesn't have a newline after `"wonderful"`, so it doesn't match the long but it does the short. Use `search_for[strcspn(search_for, "\n")] = '\0';` to safely zap the newline if it is present. – Jonathan Leffler Jan 08 '20 at 03:58
  • And `scanf()` stops at the first white space (blank, tab, newline) and doesn't include that in the string. – Jonathan Leffler Jan 08 '20 at 04:00
  • so the book is no correct about the example? – Hao Wu Jan 08 '20 at 04:02
  • @HaoWu -- that's right, the code in the book is broken. Also, `int main(void)` and `int main(int argc, char *argv[])` are the only two signatures for `main()` that are sanctioned in the Standard. Other signatures are possible, but they are _implementation-defined_. It looks like the book covers the second signature, but only uses `int main()` in place of `int main(void)` (this is the one that you should usually be using). I'd be careful with this book.... – ad absurdum Jan 08 '20 at 05:40
  • @ex nihilo thanks! It seems like I should be careful about the book – Hao Wu Jan 08 '20 at 06:50
  • @Jonathan Leffler thanks a lot! – Hao Wu Jan 08 '20 at 06:52
  • @JonathanLeffler While `strcspn` is standard C, it's quite exotic and also needlessly slow. I'd rather recommend beginners to use `search_for[strlen(search_for)-1] = '\0'` or `*strchr(search_for,'\n') = '\0';`. Faster and easier to understand. – Lundin Jan 08 '20 at 10:39
  • Both your alternatives crash when there isn’t a newline in the data, @Lundin , whereas the version using `strcspn()` works correctly regardless. I prefer the solution I suggested because it is robust. – Jonathan Leffler Jan 08 '20 at 13:49
  • @JonathanLeffler Well, obviously you'd add more error handling in a production-quality program. The `strchr` version is probably the best one for that reason. – Lundin Jan 08 '20 at 13:52
  • @Lundin—  I'll take the "it works regardless without any special precautions" version over "it works if you do it right and it is fiddly to show how to do it right so we'll short-circuit over the issues, leaving novices to wonder why things eventually go wrong". At least until someone demonstrates authoritatively that the performance is so horrible that it is unusable at any time in any program. There might conceivably be a special use case where it's a problem, but I doubt it. – Jonathan Leffler Jan 08 '20 at 14:46

1 Answers1

3

This is because fgets reads at most the length 80-1 = 79 characters (it leaves 1 character which becomes null termination) but it stops reading after the line feed \n character that gets appended to stdin after you type "wonderful" and press enter. It copies this \n character to your buffer though, so the input string is actually "wonderful\n\0", which doesn't match the data you have, because of the line feed.

Now as you happen to declare the buffer size 10 instead, fgets doesn't have room to store the \n, but still null terminates the string - that's why it works by accident.

scanf on the other hand leaves \n behind in stdin without reading it, so that's why scanf works too. However, fgets is the better and safer function, so you should keep using it.

The quick fix is to do something like:

fgets(search_for, 80, stdin); 
search_for[strlen(search_for)-1] = '\0';
find_track(search_for);

This overwrites the \n with a null terminator. After fgets you have "wonderful\n\0" in memory, and then search_for[strlen(search_for)-1] = '\0'; changes this to "wonderful\0\0".

Note that for a proper program, you should also add error handling:

if(fgets(search_for, 80, stdin) != NULL)
{
  search_for[strlen(search_for)-1] = '\0';
  find_track(search_for);
}
Lundin
  • 195,001
  • 40
  • 254
  • 396