Arrays, Strings, and Pointers

To properly understand how pointers work, and how they relate to arrays and strings, it is essential to be able to understand declarations properly. Simple declarations are easy, but once stars and square brackets start to appear together, things can get complicated.

Here are the essential things to know: That's all it takes ever, and that last example was one that most professional programmers would get a panic attack from. Don't worry if it is hard to see exactly what is happening right away. Just having this much understanding of how declarations really work puts you well ahead of the crowd. A bit of experience will make it all seem easy.


Another important thing to keep in mind is that a pointer is just a number, the numeric address of the memory location that contains the thing we're pointing to. In fact, most useful things don't fit in a single location (an integer normally takes 4, an array of 20 integers would then require 80), so a pointer is only really a pointer to the beginning of something, and a pointer to a single integer is exactly the same in every way as a pointer to an array of a million integers.
        If you have declared an array like this: int a[20];, then a pointer to the whole array is exactly the same as a pointer to its first element, so &a and &a[0] would be exactly the same things; undistinguishable in any way.
        A consequence of that fact is that if you see a declaration like this: int *p;, you can't tell whether p is supposed to be a pointer to an integer, or a pointer to a whole array of integers. Normally it doesn't matter, but sometimes it will be much easier to understand a program if you keep in mind the idea that the type int * could be read as both "pointer to integer" and "pointer to a whole bunch of integers".


Yet another important thing to be aware of is just what a name refers to. (It might sound better if a wrote "variable" instead of "name"; it would certainly make more sense, but it would be wrong, as you'll see soon.)
        Here are the important cases, starting with the really obvious one, just so that you see the pattern:

The Differences Between Arrays and Pointers

You have seen that pointers and arrays are completely interchangable, and even indistinguishable, in many circumstances. In other circumstances there are real differences.

If you make these two declarations:
        char x[80];
        char *y;
80 bytes of memory are allocated for x, and x is set to point to the first of those bytes. The name x behaves like a pointer, but it is more like one that was declared as char * const x rather than the normal char *x. It is important to remember that x is not a variable, it is a constant pointer.
        You can not say x=y; The name x refers to the address of the array, so the assignment would be trying to change the array's address, which is impossible.
        You can of course say x[0]=44; or x[7]=x[72]*3; or anything like that. If you couldn't arrays would be useless.
        And, because arrays and pointers are very similar, you can also say y[0]=44; or y[7]=y[72]*3; or anything like that, but be careful: y is just a simple variable, with just enough space to store a pointer to an array. The declaration char *y does not create an array, it just gives you a place to store a pointer to one that already exists. If you say y[3]=99 straight away, your program will go wrong. Make y point to something real before you use it.
        You can say y=x; The name x refers to the address of the array, so it behaves as a pointer; the name y refers to a variable that can hold pointers, so it is just right. Once you have said y=x, you have given y something real to point to, so it is now reasonable to say y[0]=44; or y[7]=y[72]*3; or anything like that. Until you make y point somewhere else, y[i] will be exactly the same as x[i] always.
        Remember x[0] is the same as *x, &x[0] is the same as x, &x[17] is the same as x+17, and x[17] is the same as *(x+17).
        You can make use of those equivalences to make arrays a little more convenient. If you have a large array, something like this: int large[1000]; and at some point you find that a small segment of that array (perhaps elements 360 to 369) are very frequently accessed, almost as though they were their own little 10 element sub-array buried inside the whole, you can declare a convenient extra access method: int *small=large+360;. With this, small is a pointer to integers, and the place it points to is 360 locations into the array large. So, *small is the same thing as *(large+360), which is the same thing as large[360];. Similarly, small[3] is the same thing as *(small+3), which is the same thing as *(large+360+3), which is the same thing as large[363];. Small is an array in its own right, but it does not occupy any new memory. small[0] to small[9] are just alternate, more convenient, names for large[360] to large[368].