65 66 67 68 69 70 71 72 73
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| a | b | c | d | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
Imagine this is a memory print, and the numbers are their respective addresses. If I ask you "what is at address 68?", you would reply "The letter d". Likewise, if I asked "what is at address 70?", you'd say "Nothing".
When you use pointers, this is basically what you are doing: asking the computer what are the contents of an address.
int *p; // now you can ask the computer for address
p = 65; // hey, computer, I'm gonna ask about the address 65
printf("%c", *p); // the computer access the address, then answers.
In the above example, the computer would tell you (via printf), that "the contents at address 65, which you asked me, is the letter a". From this you can see that a pointer is a "two-way" variable. You set the address it should look for when you dereference it (use the *p).
int *p;
p = 65;
// is equivalent to
int *p = 65
In the above example, you are telling the computer you are interested in the address 65. And when you dereference the pointer, you are asking the computer "what is the contents of this address you have?". When you do this, the computer tries to access that memory address. If you do not have permissions or if the address is invalid, you get a segmentation fault. (By the standard, it's undefined behavior...)
int *p; // now you can ask the computer for address
p = 70; // hey, computer, I'm gonna ask about the address 70
printf("%d", *p); // the computer access the address, then answers.
And in the above example, the computer would reply with "there is nothing at that address".