All computers have memory, also known as RAM (random access memory). For example, your computer might have 16 or 32 or 64 megabytes of RAM installed right now. RAM holds the programs that your computer is currently running along with the data they are currently manipulating (their variables and data structures). Memory can be thought of simply as an array of bytes. In this array, every memory location has its own address -- the address of the first byte is 0, followed by 1, 2, 3, and so on. Memory addresses act just like the indexes of a normal array. The computer can access any address in memory at any time (hence the name "random access memory"). It can also group bytes together as it needs to to form larger variables, arrays, and structures. For example, a floating point variable consumes 4 contiguous bytes in memory. You might make the following global declaration in a program:

float f;

This statement says, "Declare a location named f that can hold one floating point value." When the program runs, the computer reserves space for the variable f somewhere in memory. That location has a fixed address in the memory space, like this:


The variable f consumes four bytes of RAM in memory.
That location has a specific address, in this case 248,440.

While you think of the variable f, the computer thinks of a specific address in memory (for example, 248,440). Therefore, when you create a statement like this:

f = 3.14;

The compiler might translate that into, "Load the value 3.14 into memory location 248,440." The computer is always thinking of memory in terms of addresses and values at those addresses.

There are, by the way, several interesting side effects to the way your computer treats memory. For example, say that you include the following code in one of your programs:

int i, s[4], t[4], u=0;

for (i=0; i<=4; i++)
{
s[i] = i;
t[i] =i;
}
printf("s:t\n");
for (i=0; i<=4; i++)
printf("%d:%d\n", s[i], t[i]);
printf("u = %d\n", u);

The output that you see from the program will probably look like this:

s:t
1:5
2:2
3:3
4:4
5:5
u = 5

Why are t[0] and u incorrect? If you look carefully at the code, you can see that the for loops are writing one element past the end of each array. In memory, the arrays are placed adjacent to one another, as shown here:


Therefore, when you try to write to s[4], which does not exist, the system writes into t[0] instead because t[0] is where s[4] ought to be. When you write into t[4], you are really writing into u. As far as the computer is concerned, s[4] is simply an address, and it can write into it. As you can see however, even though the computer executes the program, it is not correct or valid. The program corrupts the array t in the process of running. If you execute the following statement, more severe consequences result:

s[1000000] = 5;

The location s[1000000] is more than likely outside of your program's memory space. In other words, you are writing into memory that your program does not own. On a system with protected memory spaces (UNIX, Windows 98/NT), this sort of statement will cause the system to terminate execution of the program. On other systems (Windows 3.1, the Mac), however, the system is not aware of what you are doing. You end up damaging the code or variables in another application. The effect of the violation can range from nothing at all to a complete system crash. In memory, i, s, t and u are all placed next to one another at specific addresses. Therefore, if you write past the boundaries of a variable, the computer will do what you say but it will end up corrupting another memory location.

Because C and C++ do not perform any sort of range checking when you access an element of an array, it is essential that you, as a programmer, pay careful attention to array ranges yourself and keep within the array's appropriate boundaries. Unintentionally reading or writing outside of array boundaries always leads to faulty program behavior.

As another example, try the following:

#include 

int main()
{
int i,j;
int *p; /* a pointer to an integer */
printf("%d %d\n", p, &i);
p = &i;
printf("%d %d\n", p, &i);
return 0;
}

This code tells the compiler to print out the address held in p, along with the address of i. The variable p starts off with some crazy value or with 0. The address of i is generally a large value. For example, when I ran this code, I received the following output:

0   2147478276
2147478276 2147478276

which means that the address of i is 2147478276. Once the statement p = &i; has been executed, p contains the address of i. Try this as well:

#include 

void main()
{
int *p; /* a pointer to an integer */

printf("%d\n",*p);
}

This code tells the compiler to print the value that p points to. However, p has not been initialized yet; it contains the address 0 or some random address. In most cases, a segmentation fault (or some other run-time error) results, which means that you have used a pointer that points to an invalid area of memory. Almost always, an uninitialized pointer or a bad pointer address is the cause of segmentation faults.

Having said all of this, we can now look at pointers in a whole new light. Take this program, for example:

#include 

int main()
{
int i;
int *p; /* a pointer to an integer */
p = &i;
*p=5;
printf("%d %d\n", i, *p);
return 0;
}

Here is what's happening:

The variable i consumes 4 bytes of memory. The pointer p also consumes 4 bytes (on most machines in use today, a pointer consumes 4 bytes of memory. Memory addresses are 32-bits long on most CPUs today, although there is a increasing trend toward 64-bit addressing). The location of i has a specific address, in this case 248,440. The pointer p holds that address once you say p = &i;. The variables *p and i are therefore equivalent.

The pointer p literally holds the address of i. When you say something like this in a program:

printf("%d", p);

what comes out is the actual address of the variable i.

0 comments