
I miss this. Very good times.
This is going to be a quick and dirty article about C pointers for someone with no programming background. Let's see if I can explain...
In C, when you want a number you have a few choices. The most common number type is an integer, or "int". You can also have real numbers using what are called floating point numbers if you expect a decimal, but working with pure integers is faster for the CPU to process. The C type "int" has a range, and the range kind of depends on the C compiler (the program used to turn C code into machine code). Most of the time, however, an "int" goes from -2 billion to +2 billion—or more precisely, -231 to +231. There are also "unsigned int". As the name suggests, they can not be negative, and their range is usually 0 to 232. This number isn't arbitrary. If you haven't already figured it out, the integer number range has to do with the number of bits, and in each of these cases, each number takes 32-bits. There are 8 bits in every byte, so a 32-bit values takes 4 bytes of memory to store.
One can have a list of integers, called an array. Each item in the list has an index, starting with 0. So if we had the array "list" which had 100 items ("int list[ 100 ];"), "list[ 0 ]" would be the first item, "list[ 1 ]" the second and "list[ 99 ]" the last.
Now pointers. A pointer is used to "point" to a memory location. A pointer always sits on top some other type. For example, you can have an integer pointer (int *) or an unsigned integer pointer (int unsigned *). The pointer says, rather then holding the number directly, the variable points to where the number is held.
An array is really just a pointer. In our "list" example, "list" is just a pointer to an integer—the first integer in the list. Since we know each integer takes of 4 bytes, the next integer starts 4 bytes after the pointer, the second 8 bytes after the first, and so on.
One place pointers are very useful is in what is called "memory-mapped" hardware. Memory, RAM (random access memory) or ROM (read-only memory) is what people typically refer to as memory. This is where pointers do their work—pointing to locations in RAM or ROM. A CPU's "memory space" is often much larger then the actual amount of memory in the machine. For example, a system with a 32-bit memory space can have up to 4 gigabytes of memory. But you don't need to have it all filled with RAM/ROM. Often, there are devices the CPU will have to talk to, like say a digital camera. The unused area in the memory map can be used to interface with the camera. For example, say the camera takes one picture 1024x1024 pixel wide every second, and say each pixel is a single 32-bit value (this would be a 1 mega-pixel color image, FYI). The complete image would take 4 megabyte of memory, which could be "mapped" to a specific location. In C, we can make a pointer to this location, thereby enabling the programmer to read the image. In this case, the memory might be "read only", so our pointer must not be used to change the memory—only read it. We could use "int unsigned const * pixels" for this. One other special trick can be done as well. The CPU doesn't control the camera memory, which is unique—usually the CPU controls all memory. Because of this, many C compilers take the liberty of assuming that they can take shortcuts. If you just used a value, you don't have to read it again before using it next time. However, since the CPU doesn't control this memory, that is a bad assumption. For this, there is the keyword "volatile". That means the value in this memory location could change at any time, so the CPU must read it before every use and not make assumptions. So our declaration would look like this "int unsigned volatile const * pixels". Reading it from right to left, we get: 'pixels' is a pointer to a constant (we can't change it), volatile (it may change at anytime), unsigned integer.
Now in code, when one is dealing with a pointer, one is dealing with two values: the value of what is being pointed to, and where the pointer is pointing. By default, when referencing a pointer, we are talking about where the pointer is pointing. If we want the value of what is being pointed to, we "dereference" the pointer. It looks like this:
The ampersand "&" means "address of". So "pointer" in the first line is being set to the address of "some_value"—it now points to "some_value"; The second part sets what is being pointed to by the pointer to 10. The asterisk "*" preforms the function of "dereferencing", meaning we want to deal with the value being pointed to, not where the pointer points. In this case, we have just changed "some_value" from 5 to 10.
Note that the asterisk also means multiply. However, you can not "multiply" a pointer, any more then you can multiply a sentence (or string). So having an asterisk take both meanings isn't much of a problem. But you can set both a pointer and a dereferenced pointer to an integer value.
Both of these will compile. The second one will probably warn you because this is may have been a mistake. The can be fixed with "casting" but I'm starting to exceed the scope of this article. To finish up, I will cover one last situation:
What is being set to 100? This is a case of an "uninitialized pointer". We never set a location for it to point to, and we will have no idea what memory location is being set. This was the mistake in the article on the 25th. The coder didn't mean to set the value being pointed to—they meant to set where the pointer was pointing. You can find out where an uninitialized pointer is pointing, but the result it depends on where in code it's being used.
Uninitialized pointers are very tough to trouble shoot sometimes. What's tricky is that uninitialized pointers may not always point to the same location if the code is modified (why is a little complex). So if you have one, you may not see the bad things it does right away. And if there is one in code you didn't write, you might see all kinds of strange errors that appear to come and go.
As a programmer we have developed techniques of coding to avoid ever having uninitialized pointers. And compilers can often catch when a pointer is being used before it was initialized to warn us. But clearly, this isn't always the case.
I hope that help explain a little bit more about the article, and pointers. I can answer questions in the comments.