Pointers are arguably the most difficult feature of C to understand. But, it is one of the features which make C an excellent language. In a series of articles, we will take a closer look at pointers and how they can be used in C programming.
Topics -
0. What exactly are pointers?
1. Pointer Notation and Definition
2. Some Special Pointers
3. Pointer Arithmetic
0. What exactly are pointers?
Before we go to the definition of pointers, let us understand what happens when we write
int digit = 42;
A block of memory is reserved by the compiler to hold an int
value. The name of this block is digit
and the value stored in this block is 42
. Now, to remember the block, it is assigned with an address or a location number(say, 24650).
The value of the location number is not important for us, as it is a random value. However, we can access this address using &
(ampersand) or address of operator like below.
printf("The address of digit = %d.",&digit);
/* prints "The address of digit = 24650. */
Now, we can get the value of the variable digit
from its address using another operator *
(asterisk), called indirection or dereferencing or value at address operator.
printf("The value of digit = %d.", *(&digit);
/* prints "The value of digit = 42. */
1. Pointer Definition and Notation
The address of digit
can be stored in another variable known as a pointer variable. The syntax for storing a variable's address to a pointer is :
dataType *pointerVariableName = &variableName;
For our digit
variable, this can be written as :
int *addressOfDigit = &digit;
or
int *addressOfDigit;
addressOfDigit= &digit;
This can be read as - A pointer to int
(integer) addressOfDigit
stores the address of(&)
digit
variable.
Few points to understand -
1.dataType
- We need to tell the computer what is the data type of the variable whose address we are going to store. Here, int
was the data type of digit
.
It does not mean that addressOfDigit
will store a value of type int
.
An integer pointer (like addressOfDigit
) can only store the address of variables of integer type.
int variable1;
int variable2;
char variable3;
int *addressOfVariables;
Here, we can assign the address of variable1
and variable2
to the integer pointer addressOfVariables
but not to variable3
since it is of type char
. We will need a character pointer variable to store its address.
2.*
- A pointer variable is a special variable in the sense that it is used to store an address of another variable. To differentiate it from other variables that do not store address, we use *
, as a symbol in the declaration.
Now, we can use our addressOfDigit
pointer variable to print the address and the value of digit
as below :
printf("The address of digit = %d.", addressOfDigit);
/* prints "The address of digit = 24650." */
printf("The value of digit = %d.", *addressOfDigit);
/*prints "The value of digit = 42. */
Here, *addressOfDigit
is to be read as the value at the address stored in addressOfDigit
.
Notice, we used %d
as the format identifier for addressOfDigit
. Well, this not completely correct. The correct identifier to be used would be %p
.
Using %p
, the address is displayed in hexadecimal value. But the memory address can be displayed in integers as well as octal values. Still, since it is not an entirely correct way, a warning is shown.
int num = 5;
int *p = #
printf("Address using %%p = %p",p);
printf("Address using %%d = %d",p);
printf("Address using %%o = %o",p);
The output according to compiler I'm using is -
Address using %p = 000000000061FE00
Address using %d = 6422016
Address using %o = 30377000
This is the warning shown when you use
%d
-
warning: format '%d' expects argument of type 'int', but argument 2 has type 'int *'
2. Some Special Pointers
1. Wild Pointer
char *alphabetAddress; /* uninitialised or wild pointer */
char alphabet = "a";
alphabetAddress = &alphabet; /* now, not a wild pointer */
When we defined our character pointer alphabetAddress
, we did not initialize it. Such pointers are known as wild pointers. They store a garbage value i.e. memory address of a byte that we don't know is reserved or not (remember int digit = 42;
, we reserved a memory address when we declared it).
Suppose we dereference a wild pointer and assign a value to the memory address it is pointing at. This will lead to unexpected behaviour since we will write data at a memory block that may be free or reserved.
2. Null Pointer
Now, to ensure we do not have a wild pointer, we can initialize a pointer with a NULL
value, making it a null pointer.
char *alphabetAddress = NULL /* Null pointer */
A null pointer points at nothing, or at a memory address that users can not access.
3. Void Pointer
A void pointer can be used to point at a variable of any data type. It can be reused to point at any data type we want to. It is declared as
void *pointerVariableName = NULL;
Since they are very general in nature, they are also known as generic pointers.
With their flexibility, void pointers also bring some constraints. Void pointers cannot be dereferenced as any other pointer. Appropriate typecasting is necessary.
void *pointer = NULL;
int number = 54;
char alphabet = "z";
pointer = &number;
printf("The value of number = ", *pointer); /* Compilation Error */
/* Correct Method */
printf("The value of number = ", *(int *)pointer); /* prints "The value at number = 54" */
pointer = &alphabet;
printf("The value of alphabet = ", *pointer); /* Compilation Error */
printf("The value of alphabet = ", *(char *)pointer); /* prints "The value at alphabet = z */
Similarly, void pointers need to be typecasted for performing arithmetic operations.
Void pointers are of great use in C. Library functions malloc()
and calloc()
which dynamically allocate memory, return void pointers. qsort()
, an inbuilt sorting function in C, has a function as its argument which itself takes void pointers as its argument.
4. Dangling Pointer
A dangling pointer points to a memory address which used to hold a variable. Since the address it points at is no longer reserved, using it will lead to unexpected results.
main(){
int *ptr;
ptr = (int *)malloc(sizeof(int));
*ptr = 1;
printf("%d",*ptr); /* prints 1 */
free(ptr); /* deallocation */
*ptr = 5;
printf("%d",*ptr); /* may or may not print 5 */
}
Though the memory has been deallocated by free(ptr)
, the pointer to integer ptr
still points to that unreserved memory address.
3. Pointer Arithmetic
We know by now that pointers are not like any other variable. They do not store any value but the address of memory blocks. Hence, it is quite clear that not all arithmetic operations would be valid with them. Would multiplying or dividing two pointers (having address) make sense?
Pointers have few but immensely useful valid operations -
1.you can assign the value of one pointer to another only if they are of the same type (unless typecasted or one of them being void *
.
int ManU = 1;
int *addressOfManU = &ManU;
int *anotherAddressOfManU = NULL;
anotherAddressOfManU = addressOfManU; /* Valid */
double *wrongAddressOfManU = addressOfManU; /* Invalid */
2.you can only add or subtract integers to pointers.
int myArray = {3,6,9,12,15};
int *pointerToMyArray = &myArray[0];
pointerToMyArray += 3; /* Valid */
pointerToMyArray *= 3; /* Invalid */
When you add (or subtract) an integer (say n) to a pointer, you are not actually adding (or subtracting) the integer literally. You are adding (or subtracting) n-times the size of the data type of the variable the pointer is pointing to.
int number = 5;
/* Suppose the address of number is 100 */
int *ptr = &number;
int newAddress = ptr + 3;
/* Same as ptr + 3 * sizeof(int) */
The value stored in newAddress
will not be 103, rather 112
.
3.subtraction and comparison of pointers is valid only if both are members of the same array.
int myArray = {3,6,9,12,15};
int sixthMultiple = 18;
int *pointer1 = &myArray[0];
int *pointer2 = &myArray[1];
int *pointer6 = &sixthMuliple;
/* Valid Expressions */
if(pointer1 == pointer2)
pointer2 - pointer1;
/* Invalid Expressions
if(pointer1 == pointer6)
pointer2 - pointer6
The subtraction of pointers results in the number of elements separating them.
4.you can assign or compare a pointer with NULL
.
The only exception to above rules is that the address of the first memory block after the last element of an array follows pointer arithmetic.
Pointer and arrays exist together. There is a reason why the most valid manipulations of pointers can only be done with arrays. We will discuss the above rules with arrays in the next article.
P.S. - Try to find what will be the value stored in pointerToMyArray
for valid operations in 1 and 2, if the address of myArray[0] is 100
.
The edits suggested by @pentacular have been included. Thanks to him/her.
Top comments (3)
An address is simply a pointer value, and you'll find that replacing 'address of' with 'pointer to', and 'address' with 'pointer value' will simplify things a lot.
This is not true -- you can assign where there is an implicit conversion (such as from void *), or by using a cast.
This is a clumsy way to think about it.
A pointer is an index into an array.
When you add one to a pointer, you get a pointer to the next item in that array.
When you subtract two pointers, you get the number of items between those two pointers.
Variables are effectively stored in arrays of length one.
Which is why { int i; &i + 1 } is well defined but { int i; &i + 2; } is not.
This is not quite true.
A pointer that does not have a null pointer value or points into or one beyond the end of a block of allocated memory has an undefined value, and using that value has undefined behavior.
If you are very lucky your implementation will produce an illegal memory access error.
If you are unlucky it will appear to work perfectly until, one day, it doesn't. :)
A big thank you for pointing out the mistakes.
I wanted to keep this as basic as possible and I think keeping 'address' would remind them what pointers store.
I missed this. My bad.
Though your explanation on the same is quite well put, I found this way they would never confuse as I did.
I meant "illegal memory access" in the sense it would not give a consistent result. But yeah, should have used different words.
I have made the changes. Thanks again.
Regarding pointers and addresses :)
I'd say that a pointer variable stores a pointer type value (just like any other variable).
Which may or may not be a pointer to an object.
The address of an object is a pointer value to that object.
So sometimes a pointer variable stores a pointer value that is the address of an object. :)