Related
I am trying to read from a file a non specific number of integers in pairs. I also want to skip lines that start with #. My problem is that nothing gets printed. When I tried to printf the value returned by fgets, it printed null. I would really appreciate a little help since I am not very experienced with C and I would be very grateful if you did not focus on feof since I have already read why is feof bad.
The file looks like this:
#This must
#be
#skipped
1233 14432
4943928 944949
11233 345432
And the code is:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct{
int start;
int end;
}path;
int main()
{
path* array;
array=malloc(5*sizeof(path));
if(array==NULL){
printf("Error allocating memory\n");
abort();
}
FILE* fd=fopen("File.txt","r");
if(fd==NULL){
printf("Error opening file\n");
abort();
}
char buff[200];
int counter=0;
if(fopen==NULL){
printf("Error opening file\n");
abort();
}
char c;
while(!feof(fd)||counter==6){
fgets(buff,200,fd);
c=buff[0];
if(strcmp(buff[0],"#")){
continue;
}
sscanf(&buff,"%d %d",array[counter].start,array[counter].end);
printf("%d\t%d\n",array[counter].start,array[counter].end);
counter++;
}
fclose(fd);
free(array);
return 0;
}
First, answering the title of your question: fgets() returns NULL at end of file and not when a file is empty.
Anyway, your test in the while loop is incorrect:
feof() only gives a true result when you have already tried read and you have already hit the end of file with an unsuccessful read. As read tries to give you as many bytes as it can... or none at all if end of file, the only way to get end of file condition is after you failed to read something. It is far better to check for fgets() result, as it returns NULL on being unable to read anything now. (and not in the last read) so
while(fgets(buff, sizeof buff, fd) != NULL)
or just
while(fgets(buff, sizeof buff, fd))
would be far better. Also, see how I use the sizeof operator to use the size of the used buffer, instead of repeating (and being error prone) the actual number of bytes at two places. If you decide to change the size of the buffer, you'll need also to change the actual number of bytes to read in the fgets() call, making the possibility of forgetting one of them an opportunity to run into trouble.
you order to stay in the loop only while !feof() OR when counter == 6 (first, this will make the control to enter the loop when counter is equal to 6, despite you have reached EOF or not, this cannot be correct) Think that you only get out of the loop when both conditions are false (this means feof() returns true and also counter != 6), you had better to write:
while(fgets(buff, sizeof buff, fd) && counter < max_number_of_iterations)
The test
if(strcmp(buff[0],"#"))
is also incorrect, as buff[0] is a character (indeed, it is the first character read in the buffer, and "#" is a string literal (not a character) Probably you got at least a warning from the compiler, from which you say no word. You had better to test both characters for equality, as in
if (buff[0] == '#') /* this time '#' is a character literal, not a string literal */
in the line
if (fopen == NULL)
fopen by itself is a pointer to the library function fopen(3) which is not what you want (fopen is always != NULL) but
if (fd == NULL){
(which you do before, so you had better to eliminate al this code)
you define a char c;, then initialize it to the first char of buff, and then you don't use it at all. This has no impact in your code but it's bad style and confounds maintainers in the future.
in the line sscanf(&buff, "%d %d", .... you don't need to pass &buff, while buff is already a char pointer. It is better to pass it buff.n But instead, you need to pass pointers to the variables you are reading so you need it corrected into:
sscanf(buff, "%d%d", &array[counter].start, &array[counter].end);
not doing so will make an Undefined Behaviour that will be difficult to pursue, as the use of uninitialised variables (and more on, pointers to variables) will make the code probably work at first, but fail when it has got to production for a while... this is a very serious error.
Your code, with all these errors corrected, should look like:
pru.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N (5) /* I have defined this constant so you can
* change its value without having to go all
* the code for occurrences of it and
* changing those */
typedef struct{
int start;
int end;
} path;
int main()
{
path* array = malloc(N*sizeof(path)); /* better declare and init */
if(array==NULL){
printf("Error allocating memory\n");
abort(); /* have you tried exit(EXIT_FAILURE); ?? */
}
FILE* fd=fopen("File.txt","r");
if(fd==NULL){
printf("Error opening file\n");
abort();
}
char buff[200];
int counter=0;
while(fgets(buff, sizeof buff, fd) && counter < N){
if(buff[0] == '#'){
continue;
}
sscanf(buff, "%d %d", &array[counter].start, &array[counter].end);
printf("%d\t%d\n", array[counter].start, array[counter].end);
counter++;
}
fclose(fd);
free(array);
return 0;
}
Running the code shows:
$ pru
1233 14432
4943928 944949
11233 345432
with the File.txt you posted.
Finally, a hint:
Despite of your interest in knowing only the reasons of your loop falling, and not why feof() is of no use here (and many other things you just don't ask for and that are wrong in your code), if that is actually the case, you had better to post an example that only shows the failing behaviour as recommended by the page How to create a Minimal, Complete, and Verifiable example you should have read and which I recommend you to do.
You should not check feof() in the while condition. See Why is “while ( !feof (file) )” always wrong?.
The loop should be:
while (fcounter < 5 && fgets(buff, 200, fd))
I am familiar with the sizeof operation in C, but when I use it for the string "1234abcd" it only returns 4, which I am assuming is accounting for the last 4 characters.
So how would I get this to be a string of size 8?
specific code is as follows:
FILE *in_file;
in_file = fopen(filename, "r");
if (in_file == NULL) {
printf("File does not exist\n");
return 1;
}
int val_to_inspect = 0;
fscanf(in_file, "%x", &val_to_inspect);
while (val_to_inspect != 0) {
printf("%x", val_to_inspect);
int length = sizeof val_to_inspect;
printf("%d", length);
Again, the string that is being read from the file is "1234abcd", just to clarify.
There're a couple of issues here:
sizeof operator returns the size of the object. In this case it returns the size of val_to_inspect, which is an int.
http://en.cppreference.com/w/cpp/language/sizeof
fscanf reads from a stream and interprets it. You are only scanning an integer ("%x"), not a string.
http://en.cppreference.com/w/cpp/io/c/fscanf
Lastly, if you actually had a nil-terminated string, to get its length you could use strlen().
TL;DR, to get the length of a string, you need to use strlen().
That said, be a little cautious while using sizeof, it operates on the data type. So, if you pass a pointer to it, it will return you the size of the pointer variable, not the length of the string it points to.
In several important ways, only some of which have anything to do with sizeof, you are mistaken about what your code actually does.
FILE *in_file;
in_file = fopen(filename, "r");
if (in_file == NULL)
{
printf("File does not exist\n");
return 1;
}
Kudos for actually checking whether fopen succeeded; lots of people forget to do that when they are starting out in C. However, there are many reasons why fopen might fail; the file not existing is just one of them. Whenever an I/O operation fails, make sure to print strerror(errno) so you know the actual reason. Also, error messages should be sent to stderr, not stdout, and should include the name of the affected file(s) if any. Corrected code looks like
if (in_file == NULL)
{
fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno));
return 1;
}
(You will need to add includes of string.h and errno.h to the top of the file if they aren't already there.)
int val_to_inspect = 0;
fscanf(in_file,"%x", &val_to_inspect);
This code does not read a string from the file. It skips any leading whitespace and then reads a sequence of hexadecimal digits from the file, stopping as soon as it encounters a non-digit, and immediately converts them to a machine number which is stored in val_to_expect. With the file containing 1234abcd, it will indeed read eight characters from the file, but with other file contents it might read more or fewer.
(Technically, with the %x conversion specifier you should be using an unsigned int, but most implementations will let you get away with using a signed int.)
(When you get more practice in C you will learn that scanf is broken-as-specified and also very difficult to use robustly, but for right now don't worry about that.)
while (val_to_inspect != 0) {
printf("%x", val_to_inspect);
int length = sizeof val_to_inspect;
printf("%d", length);
}
You are not applying sizeof to a string, you are applying it to an int. The size of an int, on your computer, is 4 chars, and that is true no matter what the value is.
Moreover, sizeof applied to an actual C string (that is, a char * variable pointing to a NUL-terminated sequence of characters) does not compute the length of the string. It will instead tell you the size of the pointer to the string, which will be a constant (usually either 4 or 8, depending on the computer) independent of the length of the string. To compute the length of a string, use the library function strlen (declared in string.h).
You will sometimes see clever code apply sizeof to a string literal, which does return a number related to (but not equal to!) its length. Exercise for you: figure out what that number is, and why sizeof does this for string literals but not for strings in general. (Hint: sizeof s will return a number related to s's string length when s was declared as char s[] = "string";, but not when it was declared as char *s = "string";.)
As a final note, it doesn't matter in the grand scheme of things whether you like your opening braces on their own lines or not, but pick one style and stick to it throughout the entire file. Don't put some if opening braces on their own lines and others at the end of the if line.
It's better to create own counter to find the length of "1234abcd" by reading the character by character.
FILE *in_file;
char ch;
int length=0;
in_file = fopen("filename.txt", "r");
if (in_file == NULL)
{
printf("File does not exist\n");
return 1;
}
while (1) {
ch = fgetc(in_file);
printf("%c", ch);
if (ch == EOF)
break;
length++;
}
fclose(in_file);
printf ("\n%d",length);
Everyone, thank you for all the feedback. I realize I made a lot of mistakes with the original post, but im just switching to c from c++, so a lot of the things I'm used to cant really be applied the same way. This is all tremendously helpful, it's good to have a place to go to.
Len=sizeof(your string)/sizeof(char)-1
-1 is eof character null
If you want to get length of any from specific begining index just do Len-index
I have the following piece of code. What the program does is ask the user for 2 strings. For the first one i tried out using a memmory allocated string with malloc() and used getc() in order to het the input from the user. For the second string i used an array of characters with specified size and scanf(). The issue i am having is that scanf gets the value that exceeded from the getc() used some lines of code before. How can i stop this behaviour? Also is enaString[ctr] = '\0'; and diaxwristiki[LINESIZE-1] = '\0' considered undefined behaviour? Or this is the right way of adding the null terminating character into your string?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[])
{
int LINESIZE = 15; //maximum length of array of characters
int ctr = 0;
char * enaString = NULL, xaraktiras, diaxwristiki[LINESIZE];
enaString = (char*)malloc(sizeof(char) * LINESIZE + 1);
if(enaString == NULL)//check if the memory has free blocks
{
printf("error to initillize memory");
exit(1);
}
printf("Eisagete xaraktira mikous %d :\n", LINESIZE);
do{
xaraktiras = getc(stdin);
enaString[ctr] = xaraktiras;
ctr++;
if (ctr == LINESIZE -1)
{
break;
}
}while(xaraktiras != '\n' );
enaString[ctr] = '\0'; //is this considered undefined behaviour?
enaString = NULL;
free(enaString);
printf("eisagete mia leksi diaxwrismou :\n");//ask the user for another word.fails cause it keeps the getc() value from before
scanf(" %15s",diaxwristiki);
diaxwristiki[LINESIZE-1] = '\0';//is this undefined behaviour?
printf("timi diaxwrismou %s\n", diaxwristiki);
}
To compile the information hidden in the comments below the OP's post and my own 2 cents:
#include <stdio.h>
#include <stdlib.h>
// you don't use anything from string.h in your version
//#include <string.h>
// Put simple constants here, for the preprocessor to process
#define LINESIZE 15
// you don't use the arguments, no need to put them here
int main( /*int argc, char *argv[] */ )
{
// such constants are better put into a preprocessor directive
//int LINESIZE = 15; //maximum length of array of characters
int ctr = 0;
// Sorted into three lines (three different types) better to read
// No need to initialize enaString to NULL for malloc/calloc
// It is a good idea to do for realloc(), safes you
// the initial malloc() but you do not use realloc() here
char *enaString;
// (f)getc() and scanf() return an int
int xaraktiras, ret_scanf;
char diaxwristiki[LINESIZE];
// no casting of malloc() in C
enaString = /*(char*) */ malloc(sizeof(char) * LINESIZE + 1);
if (enaString == NULL) //check if the memory has free blocks
{
// use stderr stream for error output
// (sderr might not be available but worth a try)
// UX-tip: use the same language for errors that you
// use for user interaction elsewhere
fprintf(stderr, "error to initillize memory");
// Use the macros from stdlib.h, the return values
// are OS dependent and might not be 0 and 1 respectively
exit(EXIT_FAILURE);
}
// you ask for a word of a certain size or only for a word?
// (My Greek is not very good and Google is of not much help here)
printf("Eisagete xaraktira mikous %d :\n", LINESIZE);
do {
// please be aware the getc() is in most cases implemented
// as a macro, use fgetc() if you are not sure if that is
// a problem (it is not here) because macros might get evaluated
// more than once
xaraktiras = getc(stdin);
// you need to check for EOF somewhere. Here would be a good place
if (xaraktiras == EOF) {
// try it by pressing CTRL+D instead of feeding characters to getc()
fprintf(stderr, "EOF found in getc() loop\n");
// EOF might also indicate an error, see the handling of scanf() below
// We don't bother with it now, we just exit
exit(EXIT_FAILURE);
}
// no need for a cast here
enaString[ctr] = xaraktiras;
// put it after the check, otherwise you have an undefined
// character at enaString[ctr]
// ctr++;
if (ctr == LINESIZE - 1) {
// You offered LINESIZE, have allocated LINESIZE+1, but only
// allow LINESIZE-1
// The user might be disappointed
break;
}
ctr++;
// No casting needed, because the type of a char constant is int
// (yes, that means that things like "char c='STOP'" once worked and you
// were able to look for 0x53544f50 in the memory dump. Some compilers might
// still allow for it but it is not recommended)
} while (xaraktiras != '\n');
// slurp the rest up if there were more characters given
// (check for EOF ommitted here but should be added, of course)
if(xaraktiras != '\n'){
while ((xaraktiras = getc(stdin)) != '\n');
}
// you go up to LINESIZE-1 now, so, together with the replacement of ctr++, it is OK
enaString[ctr + 1] = '\0'; //is this considered undefined behavior?
// don't just dump the painfully gathered characters, print them at least.
// That way you'll find out that you included the '\n', too, which
// might or might not have been your intent
printf("enaString = \"%s\"\n",enaString);
// To free the memory free() needs to know where it is and
// the pointer enaString points to that memory. If you set
// enaString to NULL free() does not know which memory to free
// (worse: free(NULL) is allowed) and the memory
// is left alone, crying, and is unreachable until the program ends,
// a so called "memory leak"
// enaString = NULL;
// free(enaString);
free(enaString);
// I don't know who told you so, but it is indeed a good idea to set
// the pointer to the free'd memory to NULL. Won't do anything here
// but might safe you from a lot of headaches in large programs
enaString = NULL;
printf("eisagete mia leksi diaxwrismou :\n"); //ask the user for another word.
// the variable "diaxwristiki" can hold 15 characters , "%15s" allows for 16, because
// scanf() includes `\0` (EOS, NUL, nul, or whatever the kids call it today), too!
// scanf() returns the number of elements (not characters!) read or EOF.
// for strerror()
#include <string.h>
// for errno
#include <errno.h>
// reset errno, just in case
errno = 0;
if ((ret_scanf = scanf(" %14s", diaxwristiki)) == EOF) {
// It also returns EOF in case of an error, so check for it
if (errno != 0) {
fprintf(stderr, "error in scanf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// try it by pressing CTRL+D instead of feeding characters to scanf()
fprintf(stderr, "EOF triggered by scanf()\n");
// diaxwristiki might contain rubbish at this point, clear it
diaxwristiki[0] = '\0';
}
// no need for adding EOS, scanf() already added it
// diaxwristiki[LINESIZE-1] = '\0';//is this undefined behaviour?
printf("timi diaxwrismou %s\n", diaxwristiki);
// it's "int main()", so return something.
exit(EXIT_SUCCESS);
}
I'm working on a project and I just encountered a really annoying problem. I have a file which stores all the messages that my account received. A message is a data structure defined this way:
typedef struct _message{
char dest[16];
char text[512];
}message;
dest is a string that cannot contain spaces, unlike the other fields.
Strings are acquired using the fgets() function, so dest and text can have "dynamic" length (from 1 character up to length-1 legit characters). Note that I manually remove the newline character after every string is retrieved from stdin.
The "inbox" file uses the following syntax to store messages:
dest
text
So, for example, if I have a message from Marco which says "Hello, how are you?" and another message from Tarma which says "Are you going to the gym today?", my inbox-file would look like this:
Marco
Hello, how are you?
Tarma
Are you going to the gym today?
I would like to read the username from the file and store it in string s1 and then do the same thing for the message and store it in string s2 (and then repeat the operation until EOF), but since text field admits spaces I can't really use fscanf().
I tried using fgets(), but as I said before the size of every string is dynamic. For example if I use fgets(my_file, 16, username) it would end up reading unwanted characters. I just need to read the first string until \n is reached and then read the second string until the next \n is reached, this time including spaces.
Any idea on how can I solve this problem?
#include <stdio.h>
int main(void){
char username[16];
char text[512];
int ch, i;
FILE *my_file = fopen("inbox.txt", "r");
while(1==fscanf(my_file, "%15s%*c", username)){
i=0;
while (i < sizeof(text)-1 && EOF!=(ch=fgetc(my_file))){
if(ch == '\n' && i && text[i-1] == '\n')
break;
text[i++] = ch;
}
text[i] = 0;
printf("user:%s\n", username);
printf("text:\n%s\n", text);
}
fclose(my_file);
return 0;
}
As the length of each string is dynamic then, if I were you, I would read the file first for finding each string's size and then create a dynamic array of strings' length values.
Suppose your file is:
A long time ago
in a galaxy far,
far away....
So the first line length is 15, the second line length is 16 and the third line length is 12.
Then create a dynamic array for storing these values.
Then, while reading strings, pass as the 2nd argument to fgets the corresponding element of the array. Like fgets (string , arrStringLength[i++] , f);.
But in this way you'll have to read your file twice, of course.
You can use fgets() easily enough as long as you're careful. This code seems to work:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAX_MESSAGES = 20 };
typedef struct Message
{
char dest[16];
char text[512];
} Message;
static int read_message(FILE *fp, Message *msg)
{
char line[sizeof(msg->text) + 1];
msg->dest[0] = '\0';
msg->text[0] = '\0';
while (fgets(line, sizeof(line), fp) != 0)
{
//printf("Data: %zu <<%s>>\n", strlen(line), line);
if (line[0] == '\n')
continue;
size_t len = strlen(line);
line[--len] = '\0';
if (msg->dest[0] == '\0')
{
if (len < sizeof(msg->dest))
{
memmove(msg->dest, line, len + 1);
//printf("Name: <<%s>>\n", msg->dest);
}
else
{
fprintf(stderr, "Error: name (%s) too long (%zu vs %zu)\n",
line, len, sizeof(msg->dest)-1);
exit(EXIT_FAILURE);
}
}
else
{
if (len < sizeof(msg->text))
{
memmove(msg->text, line, len + 1);
//printf("Text: <<%s>>\n", msg->dest);
return 0;
}
else
{
fprintf(stderr, "Error: text for %s too long (%zu vs %zu)\n",
msg->dest, len, sizeof(msg->dest)-1);
exit(EXIT_FAILURE);
}
}
}
return EOF;
}
int main(void)
{
Message mbox[MAX_MESSAGES];
int n_msgs;
for (n_msgs = 0; n_msgs < MAX_MESSAGES; n_msgs++)
{
if (read_message(stdin, &mbox[n_msgs]) == EOF)
break;
}
printf("Inbox (%d messages):\n\n", n_msgs);
for (int i = 0; i < n_msgs; i++)
printf("%d: %s\n %s\n\n", i + 1, mbox[i].dest, mbox[i].text);
return 0;
}
The reading code will handle (multiple) empty lines before the first name, between a name and the text, and after the last name. It is slightly unusual in they way it decides whether to store the line just read in the dest or text parts of the message. It uses memmove() because it knows exactly how much data to move, and the data is null terminated. You could replace it with strcpy() if you prefer, but it should be slower (the probably not measurably slower) because strcpy() has to test each byte as it copies, but memmove() does not. I use memmove() because it is always correct; memcpy() could be used here but it only works when you guarantee no overlap. Better safe than sorry; there are plenty of software bugs without risking extras. You can decide whether the error exit is appropriate — it is fine for test code, but not necessarily a good idea in production code. You can decide how to handle '0 messages' vs '1 message' vs '2 messages' etc.
You can easily revise the code to use dynamic memory allocation for the array of messages. It would be easy to read the message into a simple Message variable in main(), and arrange to copy into the dynamic array when you get a complete message. The alternative is to 'risk' over-allocating the array, though that is unlikely to be a major problem (you would not grow the array one entry at a time anyway to avoid quadratic behaviour when the memory has to be moved during each allocation).
If there were multiple fields to be processed for each message (say, date received and date read too), then you'd need to reorganize the code some more, probably with another function.
Note that the code avoids the reserved namespace. A name such as _message is reserved for 'the implementation'. Code such as this is not part of the implementation (of the C compiler and its support system), so you should not create names that start with an underscore. (That over-simplifies the constraint, but only slightly, and is a lot easier to understand than the more nuanced version.)
The code is careful not to write any magic number more than once.
Sample output:
Inbox (2 messages):
1: Marco
How are you?
2: Tarma
Are you going to the gym today?
This is literally the first thing I've ever written in C, so please feel free to point out all it's flaws. :) My issue, however is this: if I write the program the way I feel is cleanest, I get a broken program:
#include <sys/queue.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* Removed prototypes and non related code for brevity */
int
main()
{
char *cmd = NULL;
unsigned int acct = 0;
int amount = 0;
int done = 0;
while (done==0) {
scanf ("%s %u %i", cmd, &acct, &amount);
if (strcmp (cmd, "exit") == 0)
done = 1;
else if ((strcmp (cmd, "dep") == 0) || (strcmp (cmd, "deb") == 0))
debit (acct, amount);
else if ((strcmp (cmd, "wd") == 0) || (strcmp (cmd, "cred") == 0))
credit (acct, amount);
else if (strcmp (cmd, "fee") == 0)
service_fee(acct, amount);
else
printf("Invalid input!\n");
}
return(0);
}
void
credit(unsigned int acct, int amount)
{
}
void
debit(unsigned int acct, int amount)
{
}
void
service_fee(unsigned int acct, int amount)
{
}
As it stands, the above generates no errors at compile, but gives me a segfault when ran. I can fix this by changing the program to pass cmd by reference when calling scanf and strcmp. The segfault goes away and is replaced by warnings for each use of strcmp at compile time. Despite the warnings, the affected code works.
warning: passing arg 1 of 'strcmp' from incompatible pointer type
As an added bonus, modifying the scanf and strcmp calls allows the program to progress far enough to execute return(0), at which point the thing crashes with an Abort trap. If I swap out return(0) for exit(0) then everything works as expected.
This leaves me with two questions: why was the original program wrong? How can I fix it better than I have?
The bit about needing to use exit instead of return has me especially baffled.
This is happening because of the scanf statement.
Look how cmd is pointing to NULL. When scanf is run, it writes to the address of cmd, which is NULL, thus generating a segfault.
The solution is to create a buffer for cmd, such as:
char cmd[20];
Now, your buffer can hold 20 characters. However, you now need to worry about buffer overflows if a user enters more than 20 characters.
Welcome to C.
EDIT: Also, note that your credit, debit, and service fee functions won't work as expected as you have them written. This is because the parameters are passed by value, not by reference. This means that after the method returns, any changes will be discarded. If you want them to modify the arguments you give, try changing the methods to:
void credit(unsigned int *acct, int *amount)
And then call them like:
credit(&acct, &amt);
Doing that will pass the parameters by reference, meaning that any changes you make inside the credit function will affect the parameters, even after the function returns.
You aren't allocating memory for cmd, so it's NULL.
Try declaring it with some space:
char cmd[1000];
As others have pointed out, you haven't allocated anything for scanf to read into. But you should also test the return value of scanf:
if ( scanf ("%s %u %i", cmd, &acct, &amount) != 3 ) {
// do some error handling
}
The scanf function returns the number of succesful conversions, so if someone types in XXXX when you expect an integer you want to be able to detect and deal with it. But frankly, user interface code that uses scanf() is never really going to be proof against this sort of thing. scanf() was actually intended for reading formatted files, not random input from humans.
This :
char *cmd = NULL;
Should be:
char cmd[100];
Please note:
You should ensure that the string the user will input in cmd has length less than 100 or n
cmd is initialised to a null pointer which never points at any memory. scanf doesn't check that cmd is valid before trying to write to what cmd points to.
A preliminary solution instead creates some space for cmd to point to:
char cmd[30]; /* DANGEROUS! */
but this is a very dangerous move because you may still get segfaults if the input is longer than expected and scanf tries to write to cmd[30] and beyond.
For this reason scanf is considered unsafe and should not be used in production code. Safer alternatives include using fgets to read a line of input and sscanf to process it.
Sadly, C I/O is very difficult to get right without introducing the possibility of buffer overflows into your program. You always need to be thinking about how much memory you have available and whether it will be enough to store the longest possible input you could receive. You also need to check the return values of most I/O functions for errors.
In your example, scanf() is being passed a null pointer.
char *cmd = NULL;
scanf() won't allocate space for the string - you'll need to allocate somewhere for the string to go.
char cmd[80];
...
scanf ("%s",cmd);
Your getting a segmentation fault because scanf() is attempting to write its output to unallocated space.
Others have pointed out the error in your program, but for a better understanding of pointers, since you are just starting to learn C, look at this question at SO.
Your basic problem is that you haven't allocated memory for your string. In C, you are responsible for all memory management. If you declare variables on the stack, this is easy. With pointers, it's a little more difficult. Since you have the line char* str = NULL, when you attempt to scanf into it, you write bytes to NULL, which is illegal. What the %s specifier does is write into what str points to; it can't change str, as parameters are passed by value. This is why you have to pass &acct instead of just acct.
So how do you fix it? You need to provide memory where the read-in string can live. Something like char str[5] = "". This makes str a five-element character array, big enough to hold "exit" and its terminating zero byte. (Arrays decay into pointers at the slightest provocation, so we're fine on that front.) However, this is dangerous. If the user enters the string malicious, you're going to write "malic" into str and the bytes for "icious\0" into whatever comes after that at memory. This is a buffer overflow, and is a classic bug. The simplest way to fix it here is to require the user to enter a command of at most N letters, where N is the longest command you have; in this case, N = 4. Then you can tell scanf to read in at most four characters: scanf("%4s %u %i", cmd, &acct, &amt). The %4s says "read in at most four characters", so you can't screw up other memory. However, note that if the user enters malformed 3 4, you won't be able to find the 3 and the 4, since you'll be looking at ormed.
The reason you could do scanf("%s %u %i", &cmd, &acct, &amount) is that C is not type-safe. When you gave it &cmd, you gave it a char**; however, it was happy to treat it as a char*. Thus, it wrote bytes over cmd, so if you passed in the string exit, cmd might (if it were four bytes wide and had the appropriate endianness) be equal to 0x65786974 (0x65 = e, 0x78 = x, 0x69 = i, 0x74 = t). And then the zero byte, or any other bytes you passed in, you would start to write over random memory. If you change it at strcmp too, however, it will also treat the value of str as a string, and everything will be consistent. As for why return 0; fails but exit(0) works, I'm not sure, but I have a guess: you may have been writing over the return address of main. That's stored on the stack too, and if it happens to come after cmd in the stack layout, then you might be zeroing it or scribbling on it. Now, exit must do its cleanup manually, jumping to the right locations, etc. However, if (as I think is the case, although I'm not sure) main behaves like any other function, its return jumps to the space on the stack stored as the return address (which is probably a cleanup routine of some sort). However, since you've scribbled over that, you get an abort.
Now, there are a couple of other small improvements you could make. First, since you're treating done as a boolean, you ought to loop while (!done) { ... }. Second, the current setup requires you to write exit 1 1 to exit the program, even though the 1 1 bit shouldn't be necessary. Third, you should check to see if you have successfully read all three arguments, so you don't get errors/inconsistencies; for instance, if you don't fix this, then the input
deb 1 2
deb 3 a
Calls debit(1,2) and debit(3,2), while still leaving the a in the input to trip you up. Finally, you should exit cleanly on EOF, rather than looping forever doing the last thing you did. If we put this together, we get the following code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void credit(unsigned int acct, int amount);
void debit(unsigned int acct, int amount);
void service_fee(unsigned int acct, int amount);
int main() {
char cmd[5] = "";
unsigned int acct = 0;
int amount = 0;
int done = 0;
while (!done) {
if (feof(stdin)) {
done = 1;
} else {
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) {
done = 1;
} else {
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}
}
/* Cleanup code ... */
}
return 0;
}
/* Dummy function bodies */
void credit(unsigned int acct, int amount) {
printf("credit(%u, %d)\n", acct, amount);
}
void debit(unsigned int acct, int amount) {
printf("debit(%u, %d)\n", acct, amount);
}
void service_fee(unsigned int acct, int amount) {
printf("service_fee(%u, %d)\n", acct, amount);
}
Note that if there is no "cleanup code", you can replace all your uses of done with break and remove the declaration of done, giving the nicer loop
while (1) {
if (feof(stdin)) break;
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) break;
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}
In order to fully understand what is going on here you need to understand some basics about C pointers. I suggest you take a look here if you are really that new to C:
http://www.cprogramming.com/tutorial.html#ctutorial
The most common cause of segfaults are detailed here:
http://www.cprogramming.com/debugging/segfaults.html