-5

It is said that we'd better initialize a new pointer with NULL, or unexpected result will happen.But the truth is that when I add NULL as follows:

#include <stdio.h>
int main(){
    char *str = NULL;
    gets(str);
    printf("%s\n", str);
    return 0;
}

The outcome is:

Segmentation fault: 11

While I just leave the new pointer as it is as follows:

#include <stdio.h>
int main(){
    char *str;
    gets(str);
    printf("%s\n", str);
    return 0;
}

It's ok! I can get what I write into. I wonder the reason behind that.

user2736738
  • 30,591
  • 5
  • 42
  • 56
Eva Red
  • 569
  • 1
  • 5
  • 11
  • 3
    :) Come on - here you need to have some memory otherwise where `gets` will store those read characters. Both are undefined behavior. Both you should avoid. And moreover don't use `gets` - it's deprecated now. Use `fgets` instead. – user2736738 Mar 03 '18 at 12:49
  • The `gets` function is dangerous and have beem removed from the latest C standard. And [*undefined behavior*](https://en.wikipedia.org/wiki/Undefined_behavior) is undefined. And both your programs have it. – Some programmer dude Mar 03 '18 at 12:52
  • @Eva Red There is nothing wonderfull that a result looks strange when a program has undefined behavior.:) – Vlad from Moscow Mar 03 '18 at 12:52
  • Btw. the 2nd version is undefined behavior as well. The difference is, the 2nd version will crash in random cases (`str` is uninitialized and has a random pointer), the 1st always (at least on your platform). – Scheff's Cat Mar 03 '18 at 12:53
  • As for the difference, uninitialized local variables really *are* uninitialized. Their values are *indeterminate* and may seem almost random. The chances of `str` being `NULL` in your second program are small, which means `gets` will write to a seemingly random location in memory. – Some programmer dude Mar 03 '18 at 12:53
  • @Someprogrammerdude Thanks for your advice. I replaced the 'get' with 'scanf', but still Segmentation fault: 11.Using Null to initialize is not equal to define behavior? Does it mean that I have no choice but to use malloc to allocate memory in advance? – Eva Red Mar 03 '18 at 13:15
  • The function you call attempt to dereference the pointer, so it can write to where it points. Dereferencing a null pointer is undefined behavior as well. – Some programmer dude Mar 03 '18 at 16:11
  • See [Why `gets()` is too dangerous to be used — ever!](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used) – Jonathan Leffler Mar 03 '18 at 18:36

3 Answers3

3

As all the comments indicate:

C does not handle memory for you

you have to do that yourself.

And since you declared a pointer to a string, you have to make sure the pointer points to memory that is yours.

  • when you assigned NULL to the pointer it pointed to "forbidden" memory address zero and trying to put something there (with gets) caused the memory management to intercept your not-allowed attempt and abort your program;

  • when you did not assign anything to it, it was an uninitialized local variable that had a garbage value (points to a random address) where you were able to store the string read with gets but which can cause any random behaviour, called undefined behaviour, at any later point in your program. The memory you wrote to was maybe yours, but not managed (something else is stored there).

There are two ways to solve this:

  • ask the heap for memory (using malloc), e.g. char *str= malloc(1024);

  • declare a large enough buffer, e.g. char str[1024];.

And don't use gets. It is unsafe. Use fgets.

Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • "C does not handle memory for you" No, it does. Consider for example static memory.:) – Vlad from Moscow Mar 03 '18 at 13:08
  • Hmm...@VladfromMoscow, how to phrase that he has to handle his memory using declarations or malloc? Static memory is "handled" neither by C; you have to declare it. – Paul Ogilvie Mar 03 '18 at 13:09
  • For example the compiler accomodates string literals. initializes variables and so on.:) – Vlad from Moscow Mar 03 '18 at 13:10
  • @VladfromMoscow, We get into a phylosophical discussion, as a string literal is to me also a form of declaration. Just how to convey my notion in an educational way (that either for clarity steps on some toes or is completly correct and still understandable)? – Paul Ogilvie Mar 03 '18 at 13:13
  • @PaulOgilvie It seems that a type change should be added: char *str=(char *) malloc(1024); Or an error will pop up – Eva Red Mar 03 '18 at 14:13
  • @EvaRed, if you get an error, you are compiling as C++. Compiling as C will not give an error. – Paul Ogilvie Mar 03 '18 at 15:03
3

Both of your versions are wrong and have undefined behaviour. Undefined behaviour means that the outcome is undefined, anything can happen: a segfault for example or the appearance that the program runs without a problem.

First of all, never use gets again, it's a dangerous function that doesn't take the size of the buffer into account an can lead to buffer overflows. This function has also been deprecated in C99, so there no real reason to use it, use fgets instead.

gets expects a pointer to a char array, where it stores the string. If you pass a NULL pointer, gets does not check and writes through a NULL pointer, which is undefined behaviour.

If you do

char *str;
gets(str);

here you are only declaring a new pointer, but it is uninitialized, meaning that it's pointing at a random position in memory. In your case it seems that this random position was a valid one and hence the program has appearance that everything worked fine. You have to either initialize your pointer and make it point somewhere valid

char buffer[1024];
char *str = buffer;
fgets(str, 1024, stdin);

or you have to allocate memory with malloc & friend

char *str = calloc(1024, 1);
if(str == NULL)
{
    fprintf(stderr, "not enough memory\n");
    return;
}
fgets(str, 1024, stdin);

The reason why many people say that you should initialize a pointer with NULL is because it allows to check later if a pointer points to some valid position in memory. For example an algorithm could check if the pointer points to a valid location, then it continues, otherwise it allocates memory first and then continues. It's also a good strategy have to free the memory:

char *ptr = NULL;

if(something_is_true())
{
    // do stuff
    ptr = malloc(...);

    // more stuff
}

free(ptr);

Here if something_is_true() returns false, the free(ptr) won't end in an error, because free(NULL) is valid.

Pablo
  • 13,271
  • 4
  • 39
  • 59
1

When you initialized str to NULL, it pointed to memory address zero. In typical systems, the page at address zero in the virtual address space of normal processes is not mapped—the operating system marks it as unreadable and unwritable. Then, when your program used gets to attempt to read data to where str points, the hardware reported an error accessing memory, which the operating system and the shell reported to you as a segmentation fault.

When you did not initialize str, it happened to take on whatever value was in the memory the compiler happened to use for it. This value happened to be some address in your address space. (This was likely some pointer that was already on the stack of your program because the code that helps start your process prior to calling main was using addresses for various purposes.) Then, when you called gets, it wrote data in this memory. Obviously, that is dangerous because it writes data somewhere that your program may need for other purposes. However, in this case, you got away with it, the data was written into memory, and then it was printed.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312