Book mistake? (Head first C) - c

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?

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';
}

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);
}

Related

strstr function and multi dimensional arrays in c

#include <stdio.h>
#include <string.h>
void find_track(char *search_for);
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"
};
int main() {
char *to_search_str;
printf("Search for: ");
fgets(to_search_str, 80, stdin);
find_track(to_search_str);
return 0;
}
void find_track(char *search_for) {
int i;
for (i=0; i<5; i++) {
if (strstr(tracks[i], search_for)) {
printf("Track %d: '%s'\n", i, tracks[i]);
}
}
}
The program is supposed to search for a string in every string in the tracks multi dimensional array but the strstr() function in the find_track is always returning null no matter the input (even if we input the a sub string of a string from tracks multi dimensional array). I don't know why this is happening?
EDIT:
After correction
#include <stdio.h>
#include <string.h>
void find_track(char *search_for);
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"
};
int main() {
char to_search_str[80];
printf("Search for: ");
fgets(to_search_str, 80, stdin);
to_search_str[strlen(to_search_str)-1] = '\0';
find_track(to_search_str);
return 0;
}
void find_track(char *search_for) {
int i;
for (i=0; i<5; i++) {
if (strstr(tracks[i], search_for)) {
printf("Track %d: '%s'\n", i, tracks[i]);
}
}
}
Output
Most likely the issue with input via fgets().
You are reading into an uninitialized pointer to_search_str, which does not point to a valid memory location. In this case, you can simply change this to an array, like char to_search_str[80] = {0}; and get done with it.
You need to trim the trailing newline that's stored in the input buffer.
From the man page, (emphasis mine)
fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
A quick way of getting that done is to_search_str[strcspn(to_search_str, "\n")] = 0;, but there are more robust ways mentioned in this other answer
You aren't allocating to_search_str pointer, the char * pointer you pass to fgets as the destination buffer. Being it actually uninitialized, this causes undefined behavior that normally ends with a program crash.
You just need to allocate it, statically or dynamically.
The simplest solution consists in just defining a static array in the stack:
#include <string.h>
#define LEN 80
int main() {
char to_search_str[LEN];
printf("Search for: ");
fgets(to_search_str, LEN, stdin);
/* Remove trailing newline from the string to search */
to_search_str[strcspn(to_search_str, "\n")] = 0;
find_track(to_search_str);
return 0;
}
The size of the array is 80 because you use this number as the size parameter in fgets. Please note the use of a #define for the constant 80, making possible to change it in a easier way.
The dynamic allocation in the heap involves the use of malloc() function (and free() as soon as the array is not needed anymore):
#include <string.h>
#define LEN 80
int main() {
char * to_search_str = malloc(LEN);
printf("Search for: ");
fgets(to_search_str, LEN, stdin);
/* Remove trailing newline from the string to search */
to_search_str[strcspn(to_search_str, "\n")] = 0;
find_track(to_search_str);
free(to_search_str);
return 0;
}
Note: since fgets retains trailing newline ``\n'` in the output buffer, we have to remove it. I used the clever oneliner solution described here.
char *to_search_str; is an uninitialized pointer, writing to it will result in undefined behavior. You have to allocate memory or use an array instead char to_search_str[100]; for example.
Also don't forget that fgets will also read the newline into the buffer, which you have to remove.
This code snippet in main
char *to_search_str;
printf("Search for: ");
fgets(to_search_str, 80, stdin);
invokes undefined behavior because the pointer to_search_str is not initialized and has indeterminate value.
It seems you at least mean
char to_search_str[80];
printf("Search for: ");
fgets(to_search_str, 80, stdin);
The function fgets can append the new line character '\n' to the entered string.
You need to remove it for example the following way
to_search_str[ strcspn( to_search_str, "\n" ) ] = '\0';
The function find_track should be declared at least like
void find_track( const char *search_for);
Though it is a bad idea when a function definition relies on global variables.
Also the approach of finding relevant strings is not good. For example the user can enter a string that contains only one character 'a'. In this case all records will satisfy the condition. You should check that the searched string forms a word (a sequence of characters separated by spaces) in a string in the array.

String example doesn't behave as expected

I'm learning C and I've been following the "Head First C" book. I arrived at an example where this is the resulting code:
#include <stdio.h>
#include <string.h>
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++) {
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;
}
I've double, triple, and quadruple checked. This code is exactly the same as the example in the book. At first I made it my own so that I'm not just copying without learning, but when I didn't work I literally copied it to make sure. The code askes for a string, and then prints any tracks containing the string you gave it. In the book, when the example code is run, the string given was "town", and the code prints "Newark, Newark - A wonderful town." However, when I do the exact same thing it prints nothing but a newline.
I can't stress enough, this code is exactly the same as in the book, yet it behaves differently. Can someone see why?
The problem is in this statement
fgets(search_for, 80, stdin);
Function fgets includes in the string the new line character. You have to remove it from the string. For example
size_t n = strlen( search_for );
if ( n != 0 && search_for[n-1] == '\n' ) search_for[n-1] = '\0';
From the C Standard
Description
2 The fgets function reads at most one less than the
number of characters specified by n from the stream pointed to by
stream into the array pointed to by s. No additional characters are
read after a new-line character (which is retained) or after
end-of-file. A null character is written immediately after the last
character read into the array.

Why doesn't strcpy work?

char sentence2[10];
strncpy(sentence2, second, sizeof(sentence2)); //shouldn't I specify the sizeof(source) instead of sizeof(destination)?
sentence2[10] = '\0'; //Is this okay since strncpy does not provide the null character.
puts(sentence2);
//////////////////////////////////////////////////////////////
char *pointer = first;
for(int i =0; i < 500; i++) //Why does it crashes without this meaningless loop?!
{
printf("%c", *pointer);
if(*pointer == '\n')
putchar('\n');
pointer++;
}
So here's the problem. When I run the first part of this code, the program crashes.
However, when I add the for loop that just prints garbage values in memory locations, it does not crash but still won't strcpy properly.
Second, when using strncpy, shouldn't I specify the sizeof(source) instead of sizeof(destination) since I'm moving the bytes of the source ?
Third, It makes sense to me to add the the null terminating character after strncpy, since I've read that it doesn't add the null character on its own, but I get a warning that it's a possible out of bounds store from my pelles c IDE.
fourth and most importantly, why doesn't the simply strcpy work ?!?!
////////////////////////////////////////////////////////////////////////////////////
UPDATE:
#include <stdio.h>
#include <string.h>
void main3(void)
{
puts("\n\n-----main3 reporting for duty!------\n");
char *first = "Metal Gear";
char *second = "Suikoden";
printf("strcmp(first, first) = %d\n", strcmp(first, first)); //returns 0 when both strings are identical.
printf("strcmp(first, second) = %d\n", strcmp(first, second)); //returns a negative when the first differenet char is less in first string. (M=77 S=83)
printf("strcmp(second, first) = %d\n", strcmp(second, first)); //returns a positive when the first different char is greater in first string.(M=77 S=83)
char sentence1[10];
strcpy(sentence1, first);
puts(sentence1);
char sentence2[10];
strncpy(sentence2, second, 10); //shouldn't I specify the sizeof(source) instead of sizeof(destination).
sentence2[9] = '\0'; //Is this okay since strncpy does not provide the null character.
puts(sentence2);
char *pointer = first;
for(int i =0; i < 500; i++) //Why does it crashes without this nonsensical loop?!
{
printf("%c", *pointer);
if(*pointer == '\n')
putchar('\n');
pointer++;
}
}
This is how I teach myself to program. I write code and comment all I know about it so that
the next time I need to look up something, I just look at my own code in my files. In this one, I'm trying to learn the string library in c.
char *first = "Metal Gear";
char sentence1[10];
strcpy(sentence1, first);
This doesn't work because first has 11 characters: the ten in the string, plus the null terminator. So you would need char sentence1[11]; or more.
strncpy(sentence2, second, sizeof(sentence2));
//shouldn't I specify the sizeof(source) instead of sizeof(destination)?
No. The third argument to strncpy is supposed to be the size of the destination. The strncpy function will always write exactly that many bytes.
If you want to use strncpy you must also put a null terminator on (and there must be enough space for that terminator), unless you are sure that strlen(second) < sizeof sentence2.
Generally speaking, strncpy is almost never a good idea. If you want to put a null-terminated string into a buffer that might be too small, use snprintf.
This is how I teach myself to program.
Learning C by trial and error is not good. The problem is that if you write bad code, you may never know. It might appear to work , and then fail later on. For example it depends on what lies in memory after sentence1 as to whether your strcpy would step on any other variable's toes or not.
Learning from a book is by far and away the best idea. K&R 2 is a decent starting place if you don't have any other.
If you don't have a book, do look up online documentation for standard functions anyway. You could have learnt all this about strcpy and strncpy by reading their man pages, or their definitions in a C standard draft, etc.
Your problems start from here:
char sentence1[10];
strcpy(sentence1, first);
The number of characters in first, excluding the terminating null character, is 10. The space allocated for sentence1 has to be at least 11 for the program to behave in a predictable way. Since you have already used memory that you are not supposed to use, expecting anything to behave after that is not right.
You can fix this problem by changing
char sentence1[10];
to
char sentence1[N]; // where N > 10.
But then, you have to ask yourself. What are you trying to accomplish by allocating memory on the stack that's on the edge of being wrong? Are you trying to learn how things behave at the boundary of being wrong/right? If the answer to the second question is yes, hopefully you learned from it. If not, I hope you learned how to allocate adequate memory.
this is an array bounds write error. The indices are only 0-9
sentence2[10] = '\0';
it should be
sentence2[9] = '\0';
second, you're protecting the destination from buffer overflow, so specifying its size is appropriate.
EDIT:
Lastly, in this amazingly bad piece of code, which really isn't worth mentioning, is relevant to neither strcpy() nor strncpy(), yet seems to have earned me the disfavor of #nonsensicke, who seems to write very verbose and thoughtful posts... there are the following:
char *pointer = first;
for(int i =0; i < 500; i++)
{
printf("%c", *pointer);
if(*pointer == '\n')
putchar('\n');
pointer++;
}
Your use of int i=0 in the for loop is C99 specific. Depending on your compiler and compiler arguments, it can result in a compilation error.
for(int i =0; i < 500; i++)
better
int i = 0;
...
for(i=0;i<500;i++)
You neglect to check the return code of printf or indicate that you are deliberately ignoring it. I/O can fail after all...
printf("%c", *pointer);
better
int n = 0;
...
n = printf("%c", *pointer);
if(n!=1) { // error! }
or
(void) printf("%c", *pointer);
some folks will get onto you for not using {} with your if statements
if(*pointer == '\n') putchar('\n');
better
if(*pointer == '\n') {
putchar('\n');
}
but wait there's more... you didn't check the return code of putchar()... dang
better
unsigned char c = 0x00;
...
if(*pointer == '\n') {
c = putchar('\n');
if(c!=*pointer) // error
}
and lastly, with this nasty little loop you're basically romping through memory like a Kiwi in a Tulip field and lucky if you hit a newline. Depending on the OS (if you even have an OS), you might actually encounter some type of fault, e.g. outside your process space, maybe outside addressable RAM, etc. There's just not enough info provided to say actually, but it could happen.
My recommendation, beyond the absurdity of actually performing some type of detailed analysis on the rest of that code, would be to just remove it altogether.
Cheers!

C-Troubleshoot with gets()

When I give the first input, an extra 0 appears before gets() works. But if I remove gets(), then there is no problem. printf() can't be used because it breaks on blank space. Please give any alternative solution or what should I do?
#include <cstdio>
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
long long a,i,t,count;
int op;
char s[10000];
scanf("%lld",&t);
for(i=1;i<=t;i++)
{
gets(s);
a=atoll(&s[7]);
printf("%lld",a);
}
return 0;
}
The scanf() leaves the end-of-line character of the first line in the input stream which is then consumed by gets(). This is a common beginner's error often discussed here.
Recommendations:
Do not mix scanf() routines with gets() routines.
Except for short test programs do not use gets() (instead use fgets()) because with gets() buffer overflows may occur.
You can try adding the '\n' character when you are reading with scanf:
scanf("%lld\n",&t);
for(i=1;i<=t;i++)
{
gets(s);
a=atoll(&s[7]);
printf("%lld",a);
}
Why not:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
long long i;
long long t;
char s[10000];
if (fgets(s, sizeof(s), stdin) == 0)
return 0;
t = atoll(s);
for (i = 1; i <= t; i++)
{
if (fgets(s, sizeof(s), stdin) == 0)
break;
a = atoll(&s[7]);
printf("%lld\n", a);
}
return 0;
}
Amongst other merits, it doesn't:
print stray zeroes,
include C++ code in a purportedly C program,
contain any stray (unused) variables,
use the dangerous gets() function.
It is fair to note a couple of deficiencies:
It would produce bogus output if a data line was not at least 8 characters long; it should check strlen(s) before calling atoll(&s[7])
We'll assume that 10K is longer than any single line it will be given to read so truncated lines won't be a problem, though JSON data sometimes seems to get encoded in files without any newlines and can be humongously long (Firefox bookmark lists or backups, for example, don't even contain a single newline).
I'm sure what you're trying to do here, or what the problem is. But ...
As Greg Hewgill correctly said: NEVER use "gets()". It's a buffer overflow waiting to happen.
You CAN use "fgets()" - and it could easily solve the problem.
While you're at it, why the "scanf()", followed by "gets()", followed by "atoll()"? Can any of these inputs be merged? Or made more consistent?
Where are you checking for a valid conversion from "atoll()"? Why not just use "sscanf()" (and check the return value)?

How do I use sscanf to get names and print them out the way i want them to

I'm making a program that takes names from the user seperated by commas. The program allows the user to put as many or as little spaces as they want between the commas. So for example:
If I were to type something like
Smith, John
or
Smith,John
I would want to print out
John, Smith
The thing is though my program does not properly process the following examples above; it works if the input was something like
Smith , John
or
Smith ,John.
Here is my code:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LINESIZE 128
int get_last_first(FILE *fp);
int main (void)
{
get_last_first(stdin);
}
/*takes names in the format [LASTNAME],[FIRSTNAME]*/
int get_last_first(FILE *fp)
{
char first[LINESIZE];
char last[LINESIZE];
char line[LINESIZE];
size_t i;
while(1)
{
printf("Enter your last name followed by a comma and your first name\n");
/*if we cant read a line from stdin*/
if(!fgets(line, LINESIZE, fp))
{
clearerr(stdin);
break; /*stop the loop*/
}
/*goes through the line array and checks for non-alphabetic characters*/
for(i = 0; i < strlen(line); i++)
{
if(!isalpha(line[i]))
{
/*if it sees a space hyphen or comma, it continues the program*/
if((isspace(line[i]) || line[i] == ',') || line[i] == '-' )
{
continue;
}
else
{
return -1;
}
}
}
if(sscanf(line, "%s , %s", last, first))
{
printf("%s, %s", first, last);
return 1;
}
return 0;
}
}
Is it because I am not using sscanf properly?
sscanf() doesn't do pattern matching; a comma is a perfectly valid non-whitespace string component, so will be swallowed by a %s specification. You can use something like %[^ \t,] to match a run of characters up to whitespace or a comma.
The basic problem is outlined in geekosaur's answer - yes, you are misusing sscanf().
There are other problems in your code too.
Do not use strlen() in the test of a loop; it gets called each time and produces the same answer (unless you're hacking the string as you go). Call it once before the loop and use the saved value.
Stylistically, you have unnecessary parentheses and spaces (and miss necessary spaces) in the condition:
if((isspace(line[i]) || line[i] == ',') || line[i] == '-' )
if (isspace(line[i]) || line[i] == ',' || line[i] == '-')
There should be a space after the keyword (see the standard). There's no need to break the symmetry of the three-way condition with the extra parentheses; and there's no need for the space before the final close parenthesis. (Or, if you insist on it, then you need a balancing open space after the opening parenthesis of the condition. But please don't insist on it.)
You need test that sscanf() successfully converted two values; you just check that it did not convert 0 values. It might return 1; it might return EOF (as well as 0 or 2).
Your 'infinite loop' is broken by a break, but then the function does not return a value.
Your main() program ignores the returned value from the function - so you didn't need to the return value after all.
Stylistically, although the C99 standard permits you to leave out the return at the end of main(), I for one prefer to see it there. (You did not commit the sin of having main() return void; thank you.)
You might have noticed that I removed some stray code embedded in comments when I formatted the question. Don't keep dead code around - and don't show it off on SO.
Stylistically, I put the space between #include and <stdio.h>. Again, take a look at the C standard and notice how they write it: #include <stdio.h> and do thou likewise.
The comments tagged 'stylistically' are recommendations - the compiler is largely oblivious to spaces or lack of spaces. Nevertheless, the C standard shows all examples with spaces in #include lines and spaces after the keywords such as for and if, and the original description of C by Kernighan and Ritchie does the same, so there is solid precedent for you to follow. Generally, other people will find your code easier to read if you follow the standard conventions. The positioning of braces is more controversial - but you won't go far wrong if you follow K&R there (even though I prefer the Allman style).

Resources