1

My understanding:

  • char * c means c is pointing nothing.

  • When I type "Hello World", c is now pointing the first address of "Hello World".

  • It should print H and e, but I got "Segmentation fault: 11" error.

Can anyone please enlighten me why and how char * c = NULL; is causing an error?

Thanks in advance!

#include <stdio.h>

int main(void)
{
    char * c = NULL;
    gets(c);
    printf("%c, %c\n", c[0], c[1]);
    return 0;

}
HelloWorld
  • 21
  • 1
  • 2
    You need to give `gets` (which you should never use anyway) a buffer big enough to store the input. You can't just give it a NULL pointer. – kaylum Jan 23 '20 at 01:58
  • 2
    See [Why gets() is so dangerous it should never be used!](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used). It's not even part of the C++11 or later standard library. – David C. Rankin Jan 23 '20 at 02:15
  • @DavidC.Rankin: It was [removed from from C11 as well](https://en.cppreference.com/w/c/io/gets), which is somewhat more relevant to the OP who appears to be learning C. – ShadowRanger Jan 23 '20 at 02:24
  • Thank you so much for everyone who commented! – HelloWorld Jan 23 '20 at 02:33
  • @ShadowRanger - that's what I meant. I'm getting tag dyslexic... – David C. Rankin Jan 23 '20 at 02:36
  • Does this answer your question? [Why do I get a segmentation fault when writing to a string initialized with "char \*s" but not "char s\[\]"?](https://stackoverflow.com/questions/164194/why-do-i-get-a-segmentation-fault-when-writing-to-a-string-initialized-with-cha) –  Jan 23 '20 at 02:48
  • C uses pass by value, so it couldn't possibly work the way you described . Function calls can't change the argument – M.M Jan 23 '20 at 02:49

2 Answers2

1

gets doesn't allocate memory. Your pointer is pointing to NULL, which cannot be written to, so when gets tries to write the first character there, you seg fault.

The solution is:

  1. Use a stack or global array (char c[1000];) or a pointer to dynamically allocated memory (char *c = malloc(1000);), not a NULL pointer.
  2. Never use gets, which is intrinsically broken/insecure (it can't limit the read to match the size of the available buffer); use fgets instead.
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Thank you for your reply. May I ask one more question? If I change the first line to char * c, it works somehow. Do you know why it works? Is c is pointing random memory address in this case? – HelloWorld Jan 23 '20 at 02:02
  • Yes, if you fail to initialise a local variable, it can contain any value. So it's pointing somewhere, and somewhere in this case happens to be somewhere you can write (unfortunately -- in a larger program this will lead to memory corruption and a difficult to track down bug) . – Bernard Jan 23 '20 at 02:15
  • 1
    Also, it won't necessarily point to the same place the next time the program runs, so you could get different results from different runs. This is called *Undefined Behaviour*, and it means you've done something wrong :) – ikegami Jan 23 '20 at 02:16
  • *"it works somehow."* - no it doesn't. Your program invokes *undefined behavior*. You *think* it works; for all observation it may *appear* to work; but it's wrong. That the random, indeterminate value of an uninitialized pointer *may* by some remote chance contain an address that has enough writable space to contain whatever data you're sending isn't how you write correct code, and isn't a game of chance you want to play. Don't confuse *defined* behavior with *observed* behavior. the former leads to the latter; not the other way around. Believe me: you *want* it to crash in such cases. – WhozCraig Jan 23 '20 at 02:19
  • @HelloWorld: In practice, when it does work, it's usually by coincidence; the C runtime does some work before `main` is invoked, and that leaves garbage lying on the stack. If the garbage in the bytes associated with `c` happens to correspond to a pointer to stack, global or heap memory (possibly defunct, but in the process address space), then, while writing to it is undefined behavior by the standard, it's likely to "work", at least for short reads. I say "work", because wherever it is writing is likely to be in use for something else, so stomping heap metadata or stack values is a big risk. – ShadowRanger Jan 23 '20 at 02:19
  • To be clear, even if it "works" and even if it really isn't doing anything harmful *now*, the fact that it's undefined behavior means you have no protection at all. A minor security patch to `libc`, enabling position-independent compilation, or simple bad luck on the semi-random behavior of the runtime initialization could change what ends up in `c`, and your whole program will crash and burn, or you'll get nasal demons, or whatever (it's undefined behavior, anything goes). – ShadowRanger Jan 23 '20 at 02:21
1

char *c = NULL; declares the pointer c initialized to NULL. It is a pointer to nowhere.

Recall, A pointer is just a variable that holds the address to something else as its value. Where you normally think of a variable holding an immediate values, such as int a = 5;, a pointer would simply hold the address where 5 is stored in memory, e.g. int *b = &a;. Before you can use a pointer to cause data to be stored in memory -- the pointer must hold the address for (e.g. it must point to) the beginning of a valid block of memory that you have access to.

You can either provide that valid block of memory by assigning the address of an array to your pointer (where the pointer points to where the array is stored on the stack), or you can allocate a block of memory (using malloc, calloc or realloc) and assign the beginning address for that block to your pointer. (don't forget to free() what you allocate).

The simplest way is to declare a character array and then assign the address to the first element to your pointer (an array is converted to a pointer to the first element on access, so simply assigning the character array to your pointer is fine). For example with the array buf providing the storage and the pointer p holding the address of the first character in buf, you could do:

#include <stdio.h>
#include <string.h>     /* for strcspn & strlen */

#define MAXC 1024       /* if you need a constant, #define one (or more) */

int main (void)
{
    char buf[MAXC],                         /* an array of MAXC chars */
        *p = buf;                           /* a pointer to buf */

    if (fgets (p, MAXC, stdin)) {           /* read line from stdin */
        p[strcspn (p, "\n")] = 0;           /* trim \n by overwriting with 0 */
        if (strlen (p) > 1) {               /* validate at least 2-chars */
            printf("%c, %c\n", p[0], p[1]); /* output them */
        }
    }

    return 0;
}

(note: strcspn above simply returns the number of character in your string up to the '\n' character allowing you to simply overwrite the '\n' included by fgets() with '\0' -- which is numerically equivalent to 0)

Example Use/Output

$ ./bin/fgetsmin
Hello
H, e

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85