Stepping through programs in a debugger and inspecting the values of everything was helpful to my understanding of pointers. Also draw lots of pictures on your whiteboard to solidify your understanding. The thing that really cemented it for me was learning assembly and bringing up a MIPS from scratch...
Try stepping through this in your debugger, and draw some diagrams on your whiteboard to trace out the execution.
#include <stdio.h>
int main()
{
char c_arr[3] = {'a', 'b', '\0'}; // Array of 3 chars.
char* c_ptr = c_arr; // Now c_ptr contains the address of c_arr.
// What does it mean that c_ptr "contains the address of c_arr"?
// Underneath all this talk of "pointers" and "arrays", it's all
// just numbers stored in memory or registers. So right now, c_ptr is
// just a number stored somewhere in your computer.
printf("%p\n", c_ptr);
// I got `0xbf94393d`. You'll get something different each time you run it.
// That number (0xbf94393d) is a particular memory location. If you
// want to use the contents of that memory location, you use the *
// operator.
char ch = *c_ptr;
// Now ch holds the contents of whatever was in memory location 0xbf94393d.
// You can print it.
printf("%c\n", ch);
// You should see `a`.
// Let's say you want to work with the next memory location. Since
// the pointer is just a number, you can increment it with the ++ operator.
c_ptr++;
// Let's print it to see what it contains.
printf("%p\n", c_ptr);
// I got 0xbf94393e. No surprises here, it's just a number -- the
// next memory location after what was printed above.
// Again, if we want to work with the value we can use the *
// operator. You can put this on the left side of an assignment
// to modify the memory location.
*c_ptr = 'z';
// Since c_ptr was pointing to the middle of our array when we
// performed that assignment, we can inspect the array to see
// the change.
printf("%c\n", c_arr[1]);
// Again, c_ptr is just a number, so we can point it back to
// where it was. You could use -- for this, but I'll show -=.
c_ptr -= 1;
// We can also move by more than one. This will make the pointer
// contain the address of the last memory location in the array.
c_ptr = c_ptr + 2;
return 0;
}
Here's my attempt at a picture. This box is your computer's memory. Each location in memory is assigned a number, we call that number an address.
++++++++++++++++++++++++++++++++++++++++++++
| NAME | ADDRESS | VALUE |
+=========+==============+=================+
| c_arr | 0xbf94393d | 'a' |
| | 0xbf94393e | 'b' |
| | 0xbf94393f | '\0' |
+---------+--------------+-----------------+
| c_ptr + <someaddr> | 0xbf94393d |
+------------------------------------------+
When you access, say, c_arr[0]
, you are working with the top row in the table. Note that c_ptr
has as its value the address of the top row in the table. When you say *c_ptr
, you are telling the CPU to use 0xbf94393d
as the address to operate on. So *c_ptr = 'z'
is a bit like saying "Hey, go to 0xbf94393d and leave a 'z' there" -- on this street the addresses are really big.