Lesson 10: Pointers and Addresses
In this lesson, you examine one of the most sophisticated features of the C programming language: pointers. In fact, the power and flexibility that C provides in dealing with pointers serve to set it apart from many other programming languages.
Pointers enable you to effectively represent complex data structures, change values passed as arguments to functions, work with the memory that has been allocated "dynamically", and to more concisely and efficiently deal with arrays.
9.1 Pointers and Address
A pointer is a variable whose value is the address of another variable. You must declare a pointer before using it to store any variable address. The syntax form of a pointer variable declaration is:
type *variableName;
Here, the type is the data type for the pointer; it must be a valid C data type. the variableName is the name of the pointer variable. The asterisk (*) is used to declare a variable as a pointer. Let us take a look at some of the valid pointer declarations:
char *cp; // pointer to a character
int *ip; // pointer to an integer
float *fp; // pointer to a float
double *dp; // pointer to a double
The value stored in all pointers, whether character, integer, float, or otherwise, is the same, a long hexadecimal number that represents a memory address. The only difference between pointers of different data types is the data type of the variable or constant that the pointer points to.
Indirections through pointers
Pointers have two uses:
- A pointer is to store a memory address.
- A pointer uses the stored memory address to access those points using an indirection.
An indirection in C is denoted by the operand * followed by the name of a pointer variable. Its meaning is "access the content the pointer points to". Unfortunately, this operator is the same as the one to denote pointer data types when declaring pointer variables. It is very important to have a clear distinction between these two uses of the operator. When translating a program, the compiler will neither check whether the memory address contained in the pointer is correct, nor whether the pointed data is correct. This feature makes the design of programs using pointer a complex task.
The following program shows the use of the indirection operator:
#include <stdio.h>
int main()
{
int num1, num2;
int *ptr1, *ptr2;
ptr1 = &num1;
ptr2 = &num2;
num1 = 10;
num2 = 20;
*ptr1 = 30;
*ptr2 = 40;
*ptr2 = *ptr1;
return 0;
}
- Line 14 assigns the value 30 to the memory location stored in ptr1 through an indirection. The pointer has the address of num1, thus it is equivalent to assigning the value 30 directly to num1.
- Line 15 assigns the value 40 to the address pointed by ptr2. This assignment is equivalent to assigning the value 40 to num2.
- Line 17 contains two indirections. The right-hand side of the assignment obtains the data from the memory address stored in ptr1 (the address of num1), and this data is assigned to the memory address stored in ptr2 (the address of num2). Upon program termination, variable num2 contains the value 30 although it has not been directly assigned.
9.2 Working with Pointers and Arrays
Relationship Between Arrays and Pointers
Arrays and pointers are closely related in C. An array and a point are declared as:
int arr[3] = {10, 20, 30};
int *ptr = a;
When we declare an array, the compiler will set a block of sequential memory spaces for the array and set up a symbol table that links the array name to the address of the first element of the array. The name of the array does not have any memory space. It can be considered a constant point (const int *) without memory space. But, for the pointer, the compiler will reserve one memory space for the pointer variable, which stores a memory address that refers to the type of data stored at that address.
arr ==> 0x1000 // points to the first element of array
&arr ==> 0x1000 // points to the first element of array (same value as arr)
*arr ==> 10 // the value of arr pointed (first element)
&arr[0] ==> 0x1000 // the address of the first element of array
arr[0] ==> 10 // the value of the first element of the array
ptr ==> 0x1000 // the contain of ptr variable
&ptr ==> 0x100C // the address of ptr variable
*ptr ==> 10 // the value of ptr pointed
Note, the value of &arr[0] and arr are equal. Value in address &arr[0] is arr[0] and value in address arr is *arr. Hence, arr[0] is equivalent to *arr.
From the above results, you can see that the array variable (arr) points to the first element of the array, and the pointer (ptr) has a memory address at 0x100C, which stores the address of the array (0x1000).
In most contexts, array names decay to pointers. In simple words, array names are converted to pointers. That's the reason why you can use pointers to access elements of arrays. However, you should remember that pointers and arrays are not the same.
Exceptions to array decaying into a pointer
In most cases, array names decay into pointers. In the following cases/expressions, the array name does not decay into a pointer to its first element:
- When it is the argument of the & (address-of) operator.
- When it is the argument of the sizeof operator.
- When it is a string literal of type char [N + 1] or a wide string literal of type wchar_t [N + 1] (N is the length of the string) which is used to initialize an array, as in char str[] = "Apple"; or wchar_t wstr[] = L"Apple";.
Furthermore, in C++11, the newly introduced alignof operator doesn't let its array argument decay into a pointer either.
In C++, there are additional rules, for example, when it is passed by reference.
9.3 Working with Pointers and Structures
9.4 Pointer and Constant
Pointer and Constant
To use const:
- If the word const appears to the left of the asterisk, what is pointed to is constant.
- If the word const appears to the right of the asterisk, the pointer itself is constant.
- If const appears on both sides, both are constant.
For example:
const int *p ⇒ *p is read-only. [ p is a pointer to an integer constant ] same as a constant integer.
int const *p ⇒ *p is read-only. [ p is a pointer to a constant integer ]
int *p const ⇒ Syntax error
int * const p ⇒ p is read-only. [p is a constant pointer to an integer]. Since pointer p here is read-only, the initial value should be assigned when the p is declared,
const int *p const ⇒ Syntax error.
const int const *p ⇒ *p is read-only. [ p is a constant pointer to a constant integer]
const int * const *p ⇒ *p and p are read-only. [p is a constant pointer to a pointer to a constant integer.]
int const *p const ⇒ Syntax error.
int const int *p ⇒ Syntax error.
int const const *p ⇒ *p is read-only and equivalent to int const *p
int const * const p ⇒ *p and p are read-only. [ p is a constant pointer to a const integer] Since pointer p here is read-only, the initial value should be assigned when the p is declared,
Just for the sake of completeness for C following the others explanations, not sure for C++.
pp - pointer to pointer
p - pointer
data - the thing pointed, in examples x
bold - read-only variable
Pointer
p data - int *p;
p data - int const *p;
p data - int * const p;
p data - int const * const p;
Pointer to pointer
pp p data - int **pp;
pp p data - int ** const pp;
pp p data - int * const *pp;
pp p data - int const **pp;
pp p data - int * const * const pp;
pp p data - int const ** const pp;
pp p data - int const * const *pp;
pp p data - int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 2
int x;
x = 10;
int *p = NULL;
p = &x;
int ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 4
int const x = 10; // Definition must happen during declaration
int const * p = NULL;
p = &x;
int const **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 5
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 6
int const x = 10; // Definition must happen during declaration
int const *p = NULL;
p = &x;
int const ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 7
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 8
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
N-levels of Dereference
Just keep going, but may the humanity excommunicate you.
int x = 10;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;
int ****pppp = &ppp;
printf("%d \n", ****pppp);