C Programming Unit 4.1 Pointers
C Programming Unit 4.1 Pointers
In C, the pointer is a variable used for storing the address of a variable. The variable can be of
any type such as int, char, array, etc.
A pointer is a variable whose value is the address of another variable of the same type. The
variable's value that the pointer points to is accessed by dereferencing using the * operator.
There are different types of pointers such as null, void, wild, etc.
Syntax of Pointers in C
Just like any other variable or constant, you must declare a pointer before using it to store any
variable address. The general form of a pointer variable declaration is –
type *var-name;
Here, type is the pointer's type; it must be a valid C data type and var-name is the name of the
pointer variable. The asterisk * used to declare a pointer is the same asterisk used for
multiplication. However, in this statement the asterisk is being used to designate a variable as
a pointer.
Take a look at some of the valid pointer declarations −
int *ip; /* pointer to an integer */
double *dp; /* pointer to a double */
float *fp; /* pointer to a float */
char *ch /* pointer to a character */
The actual data type of the value of all pointers, whether integer, float, character, 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.
In the above example, a variable int i = 4 is declared, the address of variable i is 0x7766.
A pointer variable int *ptr=&i is declared. It contains the address of variable int i. The
value of *ptr will be value at address 0x7766; that value would be 4.
Example:
int *ptr1;
Explanation:
For pointer declaration in C, you must make sure that the data type you're using is a valid C
data type and that the pointer and the variable to which the pointer variable points must have
the same data type.
For example, if you want a pointer to point to a variable of data type int, i.e. int var=5 then
the pointer must also be of datatype 'int', i.e. int *ptr1=&var. The * symbol indicates that the
variable is a pointer. To declare a variable as a pointer, you must prefix it with *.
In the example above, we have done a pointer declaration and named ptr1 with the data type
integer.
Example:
int a = 5;
int *ptr1 = &a; //Method 1
int *ptr2 = ptr1; //Method 2
Method 1
We make use of the reference operator, i.e. '&' to get the memory address of a variable. It is
also known as the address-of-operator. Look at the figure below to understand what happens:
Method 2
Let us consider the case when we want another pointer to point to the same variable, then, in
that case, we can make use of this method to do the same instead of doing method 1 all over
again i.e. we simply assign the old pointer to the new pointer. Look at the figure below to
understand what happens:
Explanation:
& is a reference operator, which means it is used to get the memory location of the variable.
So 'a' is an integer variable and by doing &a, we are getting the location where a is stored in
the memory and then letting the pointer point to that location. This is the first method of
initializing a pointer in C.
The second method is to initialize a pointer and assign the old pointer to this new pointer.
The process of getting a value from a memory address pointed by a pointer is known as
dereferencing. To get the value pointed by a memory address, we utilize the unary
operator, *.
Output
1010
5
Note: When printf("%p\n",ptr1); is called the output is 10101010 as that is the memory
address stored by the pointer ptr1.
Explanation:
Simply printing ptr1 gets you the location in memory that the pointer points to, but to get the
value it points to, you need to make use of a dereference operator(*). The process of getting a
value from a memory address pointed by a pointer is known as dereferencing.
Output:
The Value of a: 5
The Address of a: 0x7ffd75fe33dc
The Value of ptr1: 5
The Address of ptr1: 0x7ffd75fe33dc
Look at the figure below to get a detailed explanation of the above output:
Example of Pointers in C
Illustration of pointers in C using following code:
#include <stdio.h>
int main()
{
int x = 42; //variable declaration
int *ptr; //pointer variable declaration
ptr = &x; //store address of variable x in pointer ptr
//printing the address
printf("Address stored in a variable ptr is: %x \n", ptr);
//printing the value
printf("Value stored in a variable ptr is: %d \n", *ptr);
return 0;
}
Output:
Address stored in a variable ptr is: a7a9b45c
Value stored in a variable ptr is: 42
Explanation
Features of Pointers:
1. Pointers save memory space.
2. Execution time with pointers is faster because data are manipulated with the
address, that is, direct access to memory location.
3. Memory is accessed efficiently with the pointers. The pointer assigns and releases
the memory as well. Hence it can be said the Memory of pointers is dynamically
allocated.
4. Pointers are used with data structures. They are useful for representing two -
dimensional and multi-dimensional arrays.
5. An array, of any type, can be accessed with the help of pointers, without considering
its subscript range.
6. Pointers are used for file handling.
7. Pointers are used to allocate memory dynamically.
8. In C++, a pointer declared to a base class could access the object of a derived class.
However, a pointer to a derived class cannot access the object of a base class.
Drawbacks of Pointers:
1. If pointers are pointed to some incorrect location then it may end up reading a
wrong value.
2. Erroneous input always leads to an erroneous output
3. Segmentation fault can occur due to uninitialized pointer.
4. Pointers are slower than normal variable
5. It requires one additional dereferences step
6. If we forgot to deallocate a memory then it will lead to a memory leak.
Uses of pointers:
1. To pass arguments by reference
2. For accessing array elements
3. To return multiple values
4. Dynamic memory allocation
5. To implement data structures
6. To do system-level programming where memory addresses are useful
Type of Pointers in C
1. NULL Pointer
2. Void Pointer
3. Wild Pointer
4. Dangling Pointer
• In the C programming language, a null pointer is a pointer that does not point to any
memory location and hence does not hold the address of any variables.
• Null indicates that the pointer is pointing to the first memory location means 0th location.
Normally, the null pointer does not point to anything.
• NULL is a macro constant defined in several header files in the C programming
language,including stdio.h, alloc.h, mem.h, stddef.h, and stdlib.h. Also, take note that
NULL should only be used when working with pointers.
Generally, we can say that a null pointer is a pointer that does not point to any object.
int *pointer_var = 0
We can assign 0 directly to the pointer so that it will not point to any of the memory locations.
#include <stdio.h>
int main(void) {
int num = 10;
int *ptr1 = #
int *ptr2;
int *ptr3=0;
if(ptr1 == 0)
printf("ptr1: NULL\n");
else
printf("ptr1: NOT NULL\n");
if(ptr2 == 0)
printf("ptr2: NULL\n");
else
printf("ptr2: NOT NULL\n");
if(ptr3 == 0)
printf("ptr3: NULL\n");
else
printf("ptr3: NOT NULL\n");
return 0;
}
Output
ptr1: NOT NULL
ptr2: NOT NULL
ptr3: NULL
Explanation: In the above example, we initialized three pointers as *ptr1, *ptr2, and *ptr3,
and we assigned a value to the num variable in *ptr1 and compared it by 0 because *ptr1 is
not equal to null, therefore it would output the result as NOT NULL, and we did not assign any
value to *ptr2 and As a result, the output will be printed as NOT NULL. We assigned value 0
to *ptr3, which is equal to null, hence the output will be NULL.
Output
It is null pointer
Explanation: We define function fun1() in the following code and pass a pointer ptrvarA to
it. When the function fun1() is called, it checks whether the passed pointer is a null pointer or
not. So we must verify if the pointer's passed value is null or not, because if it is not set to any
value, it will take the garbage value and terminate your program, resulting in the
program crashing.
In Linked List, a NULL pointer is also significant. We know that in a Linked List, we use a
pointer to connect one node to its successor node.
Because there is no successor node for the last node, the link of the last node must be set to
NULL.
int main()
{
//node structure
struct node
{
int data;
struct node *next;
};
//declaring nodes
struct node *head,*middle,*last;
return 0;
}
Output
10->20->30->NULL
The built-in malloc() method is used to allocate memory in the example below. If the malloc()
function fails to allocate memory, the result is a NULL pointer. As an outcome, a condition to
check whether the value of a pointer is null or not must be included; if the value of a pointer is
not null, memory must be allocated.
#include<stdio.h>
#include<stdlib.h>
int main()
{
int *ptr;
ptr=(int*)malloc(4*sizeof(int));
if(ptr==NULL)
{
printf("Memory is not allocated");
}
else
{
printf("Memory is allocated");
}
return 0;
}
Output:
Memory is allocated
It's usually good programming practice to set a Null value to the pointer when we don't know
the actual memory address.
Output
Output:
value of x = 10
value of y = 20
Example - 3:
#include<stdio.h>
int main ()
{
int x, y;
int *ptr = NULL;
ptr = &x;
x = 32767;
y = *ptr;
printf ("%d %d %d", x, y, *ptr);
*ptr = 0;
printf ("\n%d %d %d", x, y, *ptr);
return 0;
}
Output:
32767 32767 32767
0 32767 0
The concept of void pointer here is, we declare a pointer to point to void, meaning a generic
pointer, a pointer that has no data type associated with its own, which can point to any data
type and can store any datatype's address as will be required.
In simple words, We can assign the address of any data type to a void pointer, and can later
mould it as per our requirement
Syntax
void *pointerName; // void keyword followed by name of the pointer
We know that void pointers can store the address of any data type, but not only that, a void
pointer can be assigned to any other type of pointer as well without any explicit typecasting.
Examples:
int num = 9; // int variable
int *ptrInt = # // int pointer
// now if we try to do
ptrInt = &num2; // incorrect
ptrInt = ptrFloat; // incorrect
ptrFloat = # // incorrect
ptrFloat = ptrInt; // incorrect
return 0;
}
Output:
9
9
5.700000
5.700000
a
a
From the above program, we can see that void pointers are not associated with any specific
datatype at the time of their declaration, but we can initialize, and re-assign them to any of
the available data types as per our requirement
#include <stdio.h>
int main()
{
void *ptr;
printf("The size of the pointer ptr is %d", sizeof(ptr));
return 0;
}
The size of any type of pointer is almost the same, as, in the end, they all are just storing the
memory addresses, which is almost constant in the number of bits for any particular system
As we have understood till now, a void pointer does not have any associated data type of its
own and is capable of storing the address of any type.
We should also know that void pointers can be typecasted into any type of pointer very easily
and dynamic memory allocation also becomes convenient using void pointers. In C language
functions like malloc(), calloc() etc. are used to dynamically allocate the memory, they return
void pointers, which we can then typecast into any other type of pointer as per our requirement,
and can easily start using them
Output:
The value of num using num is 9
The value of num using ptrInt is 9
The value of num using ptrVoid is 9
Example 2:
#include <stdio.h>
int main()
{
int num = 9;
int *ptrInt = #
float num2 = 5.7;
float *ptrFloat = &num2;
void *ptrVoid;
ptrVoid = #
printf("The value of num is %d\n", *((int*)ptrVoid));
ptrVoid = &num2;
printf("The value of num is %f\n", *((float*)ptrVoid));
return 0;
}
Output:
The value of num is 9
The value of num is 5.700000
Example 3:
#include <stdio.h>
int main()
{
int intArr[] = {4, 5, 6};
void *ptrVoid = &intArr;
for (int i = 0; i < 3; i++)
{
printf("The value is %d\n", *((int*)ptrVoid));
ptrVoid = ptrVoid + sizeof(int);
}
return 0;
}
Output:
The value is 4
The value is 5
The value is 6
Example 4:
#include <stdio.h>
int main()
{
int num = 9;
float num2 = 5.7;
char ch = 'a';
void *ptrVoid;
ptrVoid = #
printf("The value of num is %d\n", *((int*)ptrVoid));
ptrVoid = &num2;
printf("The value of num is %f\n", *((float*)ptrVoid));
ptrVoid = &ch;
printf("The value of num is %c\n", *((char*)ptrVoid));
return 0;
}
Output:
The value of num is 9
The value of num is 5.700000
The value of num is a
Example:
#include <stdio.h>
int main()
{
int num = 9;
void *ptrVoid = #
printf("Dereferncing void pointer and the value found is : %d", *ptrVoid); // will give error
return 0;
}
Output:
warning: dereferencing 'void *' pointer
error: invalid use of a void expression
Output:
Firstly typecasting the void pointer and then dereferencing it, the value found is: 9
We cannot directly apply arithmetic operations on the void pointers, we need to typecast them
first to apply the pointer arithmetic
Example:
#include <stdio.h>
int main()
{
int intArr[] = {1, 2, 4, 7, 3, 8};
void *ptrVoid = intArr;
for (int i = 0; i < 6; i++)
{
// directly applying pointer arithmetic
printf("%d\n", *(ptrVoid + i));
}
return 0;
}
Output:
warning: dereferencing 'void *' pointer
error: invalid use of a void expression
#include <stdio.h>
int main()
{
int intArr[] = {1, 2, 4, 7, 3, 8};
void *ptrVoid = intArr;
for (int i = 0; i < 6; i++)
{
// typecasting then applying pointer arithmetic
printf("%d\n", *((int *)ptrVoid + i));
}
return 0;
}
Output:
1
2
4
7
3
8
In simple words, A wild pointer is a pointer that is not initialized till its first use in the program
hence by default they then hold some arbitrary memory location, and if we try to do any
operation using them, then it can cause our program to crash
ie, a pointer that store neither any valid address in it nor the NULL value, then the pointer is a
wild pointer
Syntax
int *ptr; // wild pointer
We can see that, we just have declared a pointer, but have not given any value to it, so it starts
pointing to some random arbitrary memory location, and hence is referred to as a wild pointer
What we are doing here is, we are declaring an int pointer ptr but not assigning it to any
address, so it becomes a wild pointer, now in the second line, we are trying to access that
arbitrary address which ptr is pointing to and are trying assigning value there, which is an
incorrect way as per the c standards, and then we are trying to print the value, which will result
in unexpected behaviour or segmentation fault or it can cause the program to crash
Note - some compilers may allow the above code to execute and print the value, but this
should be avoided
In simple words:
#include <stdio.h>
int main()
{
int num = 9; // line 1
int *ptr; // line 2
ptr = # // line 3
return 0;
}
If we only execute line 1 and line 2 in the above code, then ptr will be a wild pointer here, as
we are not storing any value in it, and hence will store an arbitrary memory location, But if we
execute line 3 as well then now ptr will not be a wild pointer anymore, as we are making it
point to a meaningful memory address, and therefore it will become a normal pointer.
Dangling Pointer in C
1. The pointers pointing to a deallocated memory block are known as Dangling Pointers.
This condition generates an error known as Dangling Pointer Problem. Dangling
Pointer occurs when a pointer pointing to a variable goes out of scope or when an
object/variable's memory gets deallocated.
2. Also, the occurrence of Dangling Pointers can result in some unexpected errors during
the execution of a program, so we have to make sure to avoid them while writing a
program.
3. There are ways to avoid Dangling Pointer Problems like assigning NULL to the pointer
when the memory gets deallocated.
Let us now look at a diagram which represents how a dangling pointer is created. Here, memory
occupied by an integer variable is deallocated, and the pointer pointing to the deallocated
memory acts as a Dangling Pointer (hanging freely).
• An integer pointer ptr points to an integer variable with value 5, ptr contains the address
of the variable.
• When the integer variable gets deallocated from memory, ptr shifts from a regular
pointer to a Dangling Pointer, and it points to some Invalid / Not in use location.
We can avoid these conditions by assigning NULL in case of deallocation of memory and
using static variables in case of variables having local scope.
We should assign NULL to the ptr pointer as soon as the memory block pointed by the ptr has
been deallocated using the free() function to avoid creating the dangling pointer problem in our
program.
The diagram below shows our algorithm's flow to assign NULL in a pointer as soon as the
memory is deallocated.
1. An integer memory block is allocated using the malloc() function to the ptr pointer, and
then we assign 5 to the memory block pointer by ptr pointer.
2. free(ptr); deallocates the integer memory block pointed by ptr pointer, ptr now points
to some garbage value in the memory.
3. As soon as the memory is deallocated using free(), we assign NULL in the ptr pointer.
It helps to avoid segmentation fault errors and garbage values.
Other Pointers
The following are some other pointers used in old 16-bit Intel architecture:
Near pointer
A near pointer works with data segments of memory that are in 64Kb of range. It can't access
addresses outside of that data segment. We can make any pointer a near pointer by using the
keyword 'near'. Illustration of Near pointer using following code:
#include<stdio.h>
int main() {
int x = 42;
int near * ptr = & x;
int sz = sizeof(ptr);
printf("size of ptr is %d byte", sz);
return 0;
}
Output
size of ptr is 2 byte
Far pointer
A far pointer has the size of 4 bytes (32 bit), and it can visit memory beyond the current
segment. The compiler allocates a segment register for segment address and another register
for offset within the current segment.
Output
size of ptr is 4 byte
Huge Pointer
A huge pointer is similar to a far pointer, the size of a huge pointer is also 4 bytes (32 bit),
and it can also visit memory beyond the current segment.
The main difference between huge and far pointer is of modification of segment. In the far
pointer, the segment part cannot be modified. But in the Huge pointer, the segment part can
be changed.
Pointer Arithmetic in C
Pointers variables are also known as address data types because they are used to store
the address of another variable. The address is the memory location that is assigned to
the variable. It doesn’t store any value.
Hence, there are only a few operations that are allowed to perform on Pointers in C
language. The operations are slightly different from the ones that we generally use for
mathematical calculations. The operations are:
1. Increment/Decrement of a Pointer
2. Addition of integer to a pointer
3. Subtraction of integer to a pointer
4. Subtracting two pointers of the same type
5. Comparison of pointers of the same type.
Increment/Decrement of a Pointer
For Example:
If an integer pointer that stores address 1000 is incremented, then it will increment by
4(size of an int) and the new address it will points to 1004. While if a float type
pointer is incremented then it will increment by 4(size of a float) and the new address
will be 1004.
Logic:
Next Location = Current Location + i * size_of(data type)
Output:
current address = 5deb5ffc
next address = 5deb6000
Logic:
Next Location = Current Location – i * size_of(data type)
Output
current address = d460f58c
next address = d460f588
C Pointer Addition:
Addition in pointer in C simply means to add a value to the pointer value.
Logic:
Next Location = Current Location + (Value To Add * size_of(data type))
void main()
{
int n = 10;
int *a;
a = &n;
printf("current address = %x \n",a);
a = a + 4;
printf("next address = %x \n",a);
}
Output
current address = bfdcd58c
next address = bfdcd59c
C Pointer Subtraction:
Subtraction from a pointer in C simply means to subtract a value from the pointer value.
Logic:
Next Location = Current Location – (Value To Add * size_of(data type))
Example 4: Example for C Pointer Subtraction.
#include<stdio.h>
void main()
{
int n = 10;
int *a;
a = &n;
printf("current address = %x \n",a);
a = a - 2;
printf("next address = %x \n",a);
}
Output
current address = f2fd491c
next address = f2fd4914
Pointers contain addresses. Adding two addresses makes no sense because there is no
idea what it would point to. Subtracting two addresses lets you compute the offset
between the two addresses. An array name acts like a pointer constant. The value of this
pointer constant is the address of the first element. For Example: if an array named arr
then arr and &arr[0] can be used to reference array as a pointer.
Output
12345
Let us now look at how an array is stored in our system's memory and how to declare and
initialize an array then, we will move to the relationship of pointers with arrays.
int arr[5];
The above statement will allocate 55 integer blocks and will occupy a memory of 20 Bytes in
the system (5 * 4 = 20, 5 is size of the array and 44 bytes is the space occupied by an integer
block, total = 20).
Below is the representation of how the array is stored in the system's memory. Let the base
address allocated by the system to the array is 300.
With respect to the concept of the pointer, let us see some important points related to arrays
in general :
• 'arr' serves two purposes here, first it is the name of the array and second, arr itself
represents the base address of the array i.e. 300300 in the above case, if we print the value
in arr then it will print the address of the first element in the array.
• As the array name arr itself represents the base address of the array, then by
default arr acts as a pointer to the first element of the array.
• arr is the same as &arr and &arr[0] in C Language.
• If we use dereferencing operator (*) on any of the above representations of array address,
we will get the value of the very first element of the array.
Let us look at the program below to see arr, &arr and &arr[0] means the same.
#include <stdio.h>
int main() {
int arr[5] = {3, 5, 7, 9, 11};
printf("arr : %u, Value : %d\n", arr, *arr);
printf("&arr : %u, Value : %d\n", &arr, *(arr));
printf("&arr[0] : %u, Value : %d\n", &arr[0], *( &arr[0]));
return 0;
}
Output:
[Success] Your code was executed successfully
arr : 63744176, Value : 3
&arr : 63744176, Value : 3
&arr[0] : 63744176, Value : 3
• * is a dereferencing operator used to extract the value from the address (arr + i).
• *(arr + i) is the same as arr[i] in a C Program.
• arr represents the array name and i represents the index value.
Let us look at a program to print the values and address of the array elements using the
above syntax.
#include <stdio.h>
int main()
{
int arr[5] = {2, 4, 6, 8, 10}, i;
Output:
[Success] Your code was executed successfully
[index 0] Address : 2364420656, Value : 2
[index 1] Address : 2364420660, Value : 4
[index 2] Address : 2364420664, Value : 6
[index 3] Address : 2364420668, Value : 8
[index 4] Address : 2364420672, Value : 10
Pointer to 2D Arrays
A 2-D array is an array of arrays, we can understand 2-D array as they are made up of n 1-D
arrays stored in a linear manner in the memory. 2-D arrays can also be represented in a matrix
form. In the matrix form, there are rows and columns, so let's look at the representation of a
2-D array matrix below where i represents the row number and j represents the column
number, arr is the array name.
Here, array contains 3 1-D arrays as its element, so array name arr acts as a pointer to the
1 st 1-D array i.e. arr[0] and not to the first element of the array i.e. arr[0][0]. As we know our
system's memory is organized in a sequential manner so it is not possible to store a 2 -D array
in rows and columns fashion, they are just used for the logical representation of 2 -D arrays.
In the above representation, we have combined 3 1-D arrays that are stored in the memory to
make a 2-D array, herearr[0],arr[1], arr[2] represents the base address of the respective
arrays. So, arr[0], arr[1] and arr[2] act as a pointer to these arrays and we can access the 2-D
arrays using the above array pointers.
Let us see the syntax of how we can access the 2-D array elements using pointers.
*(*(arr + i) + j)
Note : *(*(arr + i) + j) represents the element of an array arr at the index value of ith row
and jth column; it is equivalent to the regular representation of 2-D array elements as arr[i][j].
Let us look at an example, here we are initializing and printing the elements of the 2 -D array
using the pointers concept.
#include <stdio.h>
int main()
{
int arr[3][3] = {{2, 4, 6},
{0, 1, 0},
{3, 5, 7}};
int i, j;
printf("Addresses : \n");
for(i = 0; i < 3; i++)
{
for(j = 0; j < 3; j++)
{
printf("%u[%d%d] ", (*(arr + i) + j), i, j);
}
printf("\n");
}
printf("Values : \n");
for(i = 0; i < 3; i++)
{
for(j = 0; j < 3; j++)
{
printf("%d[%d%d] ", *(*(arr + i) + j), i, j);
}
printf("\n");
}
return 0;
}
OUTPUT :
[Success] Your code was executed successfully
Addresses :
4201367232[00] 4201367236[01] 4201367240[02]
4201367244[10] 4201367248[11] 4201367252[12]
4201367256[20] 4201367260[21] 4201367264[22]
Values :
2[00] 4[01] 6[02]
0[10] 1[11] 0[12]
3[20] 5[21] 7[22]
The arr[i] provides the address of the array’s ith element. Thus, the arr[0] would return the
address of the variable p, the arr[1] would return the address of the pointer q, and so on. We
use the * indirection operator to get the value present at the address. Thus, it would be like
this:
*arr[i]
Thus, *arr[0] would generate the value at the arr[0] address. In a similar manner, the *arr[1]
would generate the value at the arr[1] address, and so on.
Uses and A user creates a pointer for storing A user creates an array of pointers that
Purposes the address of any given array. basically acts as an array of multiple
pointer variables.
Allocation One can allocate these during the One can allocate these during the
run time. compile time.
Initialization You cannot initialize a pointer to You can easily initialize an array at the
at Definition the definition. definition level.
Resizing One can easily resize the allocated Once we declare the size of an array, we
memory of a pointer later at any cannot resize it any time we want
given time. according to our requirements.
Type of A typical pointer variable is The size of any given array decides the
Storage capable of storing only a single total number of variables that it can store
variable within. within.