Let us begin with a simplified picture of how memory is organized. A typical machine has an array of consecutively numbered or addressed memory cells that may be manipulated individually or in contiguous groups. One common situation is that any byte can be a char, a pair of one-byte cells can be treated as a short integer, and four adjacent bytes form a long.
Any variable in a program is stored at a specific area in memory. If you declare a variable, the compiler will allocate this variable to some consecutive memory cells to hold the value of the variable. The address of the variable is the address of the first memory cell.
One variable always has two properties:
- The address of the variable
- The value of the variable.
Consider the following Example
int i, j;
i = 3;
j = i;
Type of these two variables is integer so they are stored in 2-byte memory area. Suppose that the compiler allocates i at the FFEC address in memory and j in FFEE, we have:
| Variable | Address | Value |
|---|---|---|
| i | FFEC | 3 |
| j | FFEE | 3 |
Two different variables have different addresses. The i = j assignment affects only on the value of variables, that means the content of the memory area for j will be copied to the content of the memory area for i.
Pointers
A pointer is a group of cells (often two or four) that can hold an address. So if c is a char and p is a pointer that points to it, we could represent the situation this way:
Pointer declaration
If you declare a variable, its name is a direct reference to its value. If you have a pointer to a variable or any other object in memory, you have an indirect reference to its value. A pointer variable stores the address of another object or a function. To start out, the declaration of a pointer to an object that is not an array has the following syntax:
type * Name [= initializer];In declarations, the asterisk (*) means "pointer to". The identifier name is declared as an object with the type *, or pointer to type. * is the indirection or dereferencing operator; when applied to a pointer, it accesses the object the pointer points to.
Here is a simple Example
int *iPtr; // Declare iPtr as a pointer to int.
double *realptr; // pointer to a double
char *astring; // pointer to a character
The type int is the type of object that the pointer iPtr can point to.
To make a pointer refer to a certain object, assign it the address of the object.
For example, if iVar is an int variable, then the following assignment makes iPtr point to the variable iVar:
iPtr = &iVar; // Let iPtr point to the variable iVar.In a pointer declaration, the asterisk (*) is part of an individual declarator. We can thus define and initialize the variables iVar and iPtr in one declaration, as follows:
int iVar = 77, *iPtr = &iVar; // Define an int variable and a
// pointer to it.
The second of these two declarations initializes the pointer iPtr with the address of the variable iVar, so that iPtr points to iVar. Figure 4.1. illustrates one possible arrangement of the variables iVar and iPtr in memory. The addresses shown are purely fictitious examples. As Figure 4.1. shows, the value stored in the pointer iPtr is the address of the object iVar.
It is often useful to output addresses for verification and debugging purposes. The printf() functions provide a format specifier for pointers: %p. The following statement prints the address and contents of the variable iPtr:
printf("Value of iPtr (i.e. the address of iVar): %p\n"
"Address of iPtr: %p\n", iPtr, &iPtr);
The size of a pointer in memory given by the expression sizeof(iPtr)
& and * operators
The unary operator & gives the address of an object, so the statement
p = &c; assigns the address of c to the variable p, and p is said to “point to” c. The & operator only applies to objects in memory: variables and array elements. It cannot be applied to expressions, constants, or register variables.
* is the operator that retrieves the value stored at the address held in the pointer. The indirection operator * yields the location in memory whose address is stored in a pointer. If ptr is a pointer, then *ptr designates the object that ptr points to. Using the indirection operator is sometimes called dereferencing a pointer. The type of the pointer determines the type of object that is assumed to be at that location in memory. For example, when you access a given location using an int pointer, you read or write an object of type int.
The indirection operator * is a unary operator; that is, it has only one operand.ptr points to the variable x. Hence the expression *ptr is equivalent to the variable x itself.
Example 1
double x, y, *ptr; // Two double variables and a pointer to double.
ptr = &x; // Let ptr point to x.
*ptr = 7.8; // Assign the value 7.8 to the variable x.
*ptr *= 2.5; // Multiply x by 2.5.
y = *ptr + 0.5; // Assign y the result of the addition x + 0.5.
Do not confuse the asterisk (*) in a pointer declaration with the indirection operator. The syntax of the declaration can be seen as an illustration of how to use the pointer.
double *ptr;As declared here, ptr has the type double * (read: "pointer to double"). Hence the expression *ptr would have the type double.
Of course, the indirection operator * must be used with only a pointer that contains a valid address. This usage requires careful programming! Without the assignment ptr = &x in the listing above, all of the statements containing *ptr would be senseless dereferencing an undefined pointer value and might well cause the program to crash.
Pointer Assignment
Since pointers are variables, they can be used without dereferencing. Pointer assignment between two pointers makes them point to the same pointee. So the assignment iq = ip; copies the contents of ip into iq, thus making iq point to whatever ip pointed to. It makes iq point to the same pointee as ip. Pointer assignment does not touch the pointees. It just changes one pointer to have the same reference as another pointer. After pointer assignment, the two pointers are said to be "sharing" the pointee.
Example Consider the following programs:
main()
{
int i = 3, j = 6;
int *p1, *p2;
p1 = &i;
p2 = &j;
*p1 = *p2;
}
and
main()
{
int i = 3, j = 6;
int *p1, *p2;
p1 = &i;
p2 = &j;
p1 = p2;
}
Suppose the values of variables before executing the last assignment are
| Variable | Address | Value |
|---|---|---|
| i | FFEC | 3 |
| j | FFEE | 6 |
| p1 | FFDA | FFEC |
| p2 | FFDC | FFEE |
After the assignment *p1 = *p2; for the first program:
| Variable | Address | Value |
|---|---|---|
| i | FFEC | 6 |
| j | FFEE | 6 |
| p1 | FFDA | FFEC |
| p2 | FFDC | FFEE |
While the assignment p1 = p2 for the second program results
| Variable | Address | Value |
|---|---|---|
| i | FFEC | 3 |
| j | FFEE | 6 |
| p1 | FFDA | FFEE |
| p2 | FFDC | FFEE |
Initializing Pointers
Pointer variables with automatic storage duration start with an undefined value, unless their declaration contains an explicit initializer. You can initialize a pointer with the following kinds of initializers:
- A null pointer constant.
- A pointer to the same type, or to a less qualified version of the same type.
- A void pointer, if the pointer being initialized is not a function pointer. Here again, the pointer being initialized can be a pointer to a more qualified type.
Operators on Pointers
Besides using assignments to make a pointer refer to a given object or function, you can also modify an object pointer using arithmetic operations. When you perform pointer arithmetic, the compiler automatically adapts the operation to the size of the objects referred to by the pointer type.
You can perform the following operations on pointers to objects:
- Adding an integer to, or subtracting an integer from, a pointer.
- Subtracting one pointer from another.
- Comparing two pointers.
If ip points to the integer x, then *ip can occur in any context where x could, so
*ip = *ip + 10; The unary operators * and & bind more tightly than arithmetic operators, so the assignment
y = *ip + 1 takes whatever ip points at, adds 1, and assigns the result to y, while
*ip += 1 increments what ip points to, as do
++*ip and
(*ip)++ The parentheses are necessary in this last example; without them, the expression would increment ip instead of what it points to, because unary operators like * and ++ associate right to left.
When you subtract one pointer from another, the two pointers must have the same basic type, although you can disregard any type. Furthermore, you may compare any pointer with a null pointer constant using the equality operators (== and !=), and you may compare any object pointer with a pointer to void.
Pointer to pointer
A pointer variable is itself an object in memory, which means that a pointer can point to it. To declare a pointer to a pointer , you must use two asterisks, as in the following Example
char c = 'A', *cPtr = &c, **cPtrPtr = &cPtr;The expression *cPtrPtr now yields the char pointer cPtr, and the value of **cPtrPtr is the char variable c. The diagram in Figure X illustrates these references.
NULL Pointers
There are times when it’s necessary to have a pointer that doesn’t point to anything. A null pointer is what results when you convert a null pointer constant to a pointer type. A null pointer constant is an integer constant expression with the value 0, or such an expression cast as the type void *.
Null pointers are implicitly converted to other pointer types as necessary for assignment operations, or for comparisons using == or !=. Hence no cast operator is necessary in the previous example.
void Pointers
A pointer to void, or void pointer for short, is a pointer with the type void *. As there are no objects with the type void, the type void * is used as the all-purpose pointer type. In other words, a void pointer can represent the address of any object but not its type. To access an object in memory, you must always convert a void pointer into an appropriate object pointer.






