Module5 Pointers
Module5 Pointers
Chapter - 5
Pointer
Prepared by Mrs. Minakshi Vikas Gaonkar INFORMATION TECHNOLOGY,SLRTCE.
Syllabus:
Fundamentals of pointers
Declaration, initialization and dereferencing of pointers
Operations on Pointers
Concept of dynamic memory allocation
1
POINTERS 2
Figure 5.1: A normal variable stores a value; a pointer variable stores an address.
5.1.1 Basics
The general syntax to declare a pointer is:
data_type *pointer_name;
Here, data_type specifies the type of variable whose address will be stored in the pointer, and
the * symbol (the indirection or dereference operator, when used in a declaration) tells the
compiler that the variable being declared is a pointer rather than an ordinary variable.
Pointers are strongly typed in C. A pointer declared to point to an int can only correctly store
the address of an int variable; a pointer to float can only store the address of a float variable,
and so on. This typing is what allows the compiler to know how many bytes to read or write
when the pointer is dereferenced, and how far to move the pointer during pointer arithmetic.
2
POINTERS 3
Pointer Type Rule: A pointer's declared type must match the type of the variable it points to.
Assigning the address of an int variable to a float pointer is a type mismatch and, although
some compilers only warn about it, it should always be avoided.
Default Value: If a pointer is declared but not assigned a value, it contains a garbage address
left over in memory - it does not point anywhere meaningful. Using such a pointer before
assigning it a valid address leads to undefined behaviour (see Wild Pointers, section 5.2.4).
int a = 10;
int *p = &a; // p now holds the address of a
Restrictions on &
The & operator can only be applied to variables that have a memory location (an
“lvalue”).
3
POINTERS 4
4
POINTERS 5
data_type *pointer_name;
data_type - the type of variable whose address the pointer will hold (e.g., int, char, float,
or a struct).
* - the indirection operator that marks the identifier as a pointer rather than an ordinary
variable.
#include <stdio.h>
int main()
{
// Normal variable
int var = 10;
return 0;
}
Output: 0x7fffffffe9cc
This hexadecimal value (beginning with 0x) is the memory address stored inside ptr - it is not
the value 10 that var holds, but the location where var lives in memory.
5
POINTERS 6
pointer_name = &variable;
#include <stdio.h>
int main() {
int var = 10;
int *ptr = &var; // Store address of var
Output: 10
1. NULL Pointer
A NULL pointer does not point to any valid memory location. It is created by assigning the
value NULL (defined in <stdio.h>/<stdlib.h>, conventionally 0) to a pointer variable.
A pointer of any type can be assigned NULL to indicate that it is not currently pointing to
any object.
Checking whether a pointer is NULL before dereferencing it is a standard defensive
practice that prevents invalid memory access.
#include <stdio.h>
6
POINTERS 7
int main()
{
int *ptr = NULL; // Null pointer
return 0;
}
2. Void Pointer
A void pointer is a pointer with no associated data type. It is often called a generic pointer
because it can store the address of a variable of any type.
A void pointer can point to a variable of any type, but it must be type-cast to an
appropriate pointer type before it is dereferenced.
It is commonly used in generic functions and memory-management routines (such as
malloc(), which returns void *) where the exact data type is not known in advance.
#include <stdio.h>
int main() {
void *ptr; // Void pointer
return 0;
}
3. Wild Pointer
A wild pointer is a pointer that has been declared but not initialized with any valid address. It
contains whatever garbage value happened to be in that memory location when the program
started. Dereferencing a wild pointer results in undefined behaviour.
Dereferencing a wild pointer can crash the program, corrupt memory, or produce
unpredictable results.
Always initialize a pointer - either with a valid address or with NULL - before it is used.
#include <stdio.h>
int main() {
int *ptr; // Wild pointer - not yet initialized
return 0;
}
4. Dangling Pointer
A dangling pointer refers to a memory location that has already been freed or deallocated.
Accessing memory through a dangling pointer leads to undefined behaviour, because that
memory may now belong to something else entirely.
7
POINTERS 8
Dereferencing a dangling pointer can cause crashes, silently wrong results, or memory-
access errors that are extremely hard to reproduce and debug.
Dangling pointers most often occur when dynamically allocated memory is freed but the
pointer that referenced it is not reset to NULL.
Example 5.5: A pointer becomes dangling after free(), fixed by resetting to NULL
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
return 0;
}
Void pointer Any data type (generic) Generic functions, malloc() return type
Dangling pointer Freed / invalid memory Used after free() without resetting
8
POINTERS 9
Pointer operations manipulate memory addresses rather than the data stored at them. The
primary operations are dereferencing (accessing the value at an address), pointer arithmetic
(incrementing or decrementing an address), assignment (making a pointer point to a new
variable), and comparison (checking the relationship between two addresses).
5.3.1 Pointer Initialization and Assignment
Pointer initialization means assigning the address of a variable to a pointer at the time it is
declared, or shortly after. A pointer should always be initialized before it is used.
pointer = &variable;
#include <stdio.h>
int main()
{
int num = 50;
int *ptr;
ptr = #
return 0;
}
#include <stdio.h>
int main()
{
int x = 100;
int *p1, *p2;
p1 = &x;
p2 = p1;
printf("%d\n", *p2);
return 0;
}
Output: 100
5.3.2 Dereferencing
Dereferencing means accessing (or modifying) the value stored at the address contained in a
pointer, using the dereference operator (*).
*pointer
#include <stdio.h>
int main()
{
int num = 25;
int *ptr = #
*ptr = 80;
printf("New value = %d\n", num);
return 0;
}
Output: Value = 25
New value = 80
10
POINTERS 11
Expression Address
ptr 1000
ptr + 1 1004
ptr + 2 1008
The pointer moves by 4 bytes per step (the size of an int) rather than by 1 byte, because the
compiler automatically scales the arithmetic by sizeof(data_type).
Figure 5.2: Pointer arithmetic on an int array - each step advances by sizeof(int) bytes.
#include <stdio.h>
int main()
{
int arr[3] = {10, 20, 30};
11
POINTERS 12
printf("%d\n", *ptr);
ptr++;
printf("%d\n", *ptr);
return 0;
}
Output: 10
20
#include <stdio.h>
int main()
{
int arr[3] = {10, 20, 30};
int *ptr = &arr[2];
printf("%d\n", *ptr);
ptr--;
printf("%d\n", *ptr);
return 0;
}
Output: 30
20
#include <stdio.h>
int main()
{
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;
12
POINTERS 13
ptr = ptr + 3;
printf("%d\n", *ptr);
return 0;
}
Output: 40
Similarly, ptr = ptr - 2; moves the pointer two elements backward.
#include <stdio.h>
int main()
{
int arr[5];
int *p1 = &arr[4];
int *p2 = &arr[1];
printf("%ld\n", p1 - p2);
return 0;
}
Output: 3
if (p1 == p2)
printf("Same Address");
if (p1 != p2)
printf("Different Address");
13
POINTERS 14
if (ptr != NULL)
{
printf("%d", *ptr);
}
2. Multiplying Pointers
3. Dividing Pointers
14
POINTERS 15
p1 & p2;
p1 | p2; // Not allowed
Reason: pointer arithmetic supports only integer offsets, since a pointer must always point to
the start of a whole element.
15
POINTERS 16
Dynamic Memory Allocation (DMA) is the process of allocating and releasing memory
during program execution (at run time), according to the program's actual requirements,
rather than fixing the amount of memory at compile time.
In static allocation - ordinary variable and array declarations - the size of memory is decided
when the program is compiled and cannot change afterwards. Dynamic memory allocation
instead lets a program request exactly the amount of memory it needs while it is running,
which makes programs more flexible and memory-efficient, especially when the amount of
data to be processed is not known in advance.
Dynamically allocated memory comes from a region called the heap, which is distinct from
the stack where ordinary local variables live. Memory obtained from the heap remains valid
until it is explicitly released with free(), even after the function that allocated it has returned -
which is precisely what makes it useful for data structures that must outlive a single function
call.
Dynamic memory allocation in C is performed using functions declared in the <stdlib.h>
header file.
#include <stdlib.h>
Function Purpose
16
POINTERS 17
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
ptr = (int *)malloc(5 * sizeof(int));
if (ptr == NULL)
{
printf("Memory allocation failed");
return 1;
}
printf("Enter 5 numbers:\n");
for (int i = 0; i < 5; i++)
scanf("%d", &ptr[i]);
printf("Numbers are:\n");
for (int i = 0; i < 5; i++)
printf("%d ", ptr[i]);
free(ptr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
ptr = (int *)calloc(5, sizeof(int));
if (ptr == NULL)
{
printf("Memory allocation failed");
return 1;
17
POINTERS 18
free(ptr);
return 0;
}
Output: 0 0 0 0 0
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
ptr = (int *)malloc(3 * sizeof(int));
ptr = (int *)realloc(ptr, 6 * sizeof(int));
Note: realloc() may move the block to a new address if it cannot be resized in place. Always
assign the result to a temporary pointer first in production code, so the original pointer is not
lost if realloc() returns NULL.
18
POINTERS 19
free(ptr);
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
ptr = (int *)malloc(sizeof(int));
*ptr = 100;
printf("%d\n", *ptr);
free(ptr);
ptr = NULL;
return 0;
}
free(ptr);
ptr = NULL;
19
POINTERS 20
Figure 5.3: The dynamic memory allocation lifecycle, from request to release.
20
POINTERS 21
#include <stdio.h>
int main()
{
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // same as ptr = &arr[0];
return 0;
}
Output: 10 20 30 40 50
Expression Meaning
21
POINTERS 22
#include <stdio.h>
int main()
{
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // pointer to an array of 3 ints (one row)
return 0;
}
Output: 1 2 3 4 5 6
22
POINTERS 23
#include <stdio.h>
int main()
{
char str[] = "Hello";
char *p = str;
return 0;
}
Output: Hello
Function Description
23
POINTERS 24
24
POINTERS 25
A pointer can itself be stored at an address, which means another pointer can point to it. A
pointer that stores the address of another pointer is called a pointer to pointer, or a double
pointer, and is declared with two asterisks.
data_type **pointer_to_pointer;
Figure 5.4: A double pointer pp stores the address of p, which stores the address of var.
Example 5.21: Declaring and using a double pointer
#include <stdio.h>
int main()
{
int var = 25;
int *p = &var; // p points to var
int **pp = &p; // pp points to p
return 0;
}
25
POINTERS 26
Just as an array can hold integers or characters, it can also hold pointers. An array of pointers
is especially useful for handling a collection of strings, since each element of the array can
point to a string of a different length.
data_type *array_name[size];
#include <stdio.h>
int main()
{
char *names[3] = {"Amit", "Priya", "Rahul"};
return 0;
}
Output: Amit
Priya
Rahul
Here, names is an array of three char * elements. Each element stores the address of the first
character of a different string literal, which avoids the wasted space that a fixed-width two-
dimensional character array would require.
26
POINTERS 27
Figure 5.5: Call by value copies the data; call by reference passes the address, so changes
are visible to the caller.
Example 5.23: Call by value vs call by reference
#include <stdio.h>
void modifyByValue(int x)
{
x = 20; // only the local copy changes
}
int main()
{
int a = 10;
modifyByValue(a);
printf("After call by value: %d\n", a);
modifyByReference(&a);
27
POINTERS 28
return 0;
}
#include <stdio.h>
int main()
{
int nums[4] = {1, 2, 3, 4};
doubleValues(nums, 4);
return 0;
}
Output: 2 4 6 8
return_type (*pointer_name)(parameter_types);
28
POINTERS 29
#include <stdio.h>
int main()
{
int (*funcPtr)(int, int) = add;
Output: 8
Note: The parentheses around *pointer_name in a function pointer declaration are required.
Without them, int *funcPtr(int, int) would instead declare a function that returns an int *,
which is an entirely different meaning.
#include <stdio.h>
struct Student
{
char name[20];
int roll;
};
int main()
{
struct Student s1 = {"Anita", 101};
struct Student *sp = &s1;
return 0;
}
29
POINTERS 30
struct Node
{
int data;
struct Node *next; // pointer to the next node
};
30
POINTERS 31
Double free - calling free() twice on the same pointer, which corrupts the memory
manager's internal bookkeeping.
ptr = malloc(n * sizeof(*ptr)); // safer than sizeof(int), stays correct if type changes
Advantages Disadvantages
Enable efficient, direct access to memory Incorrect use can corrupt memory or crash
the program
Allow dynamic memory allocation at run Require careful, explicit release of memory
time (no automatic garbage collection)
Make call by reference possible Pointer errors are often hard to detect and
debug
Enable dynamic data structures (lists, trees, Code that uses pointers heavily can be harder
graphs) to read and maintain
Allow efficient array and string handling Portability issues can arise from assumptions
about pointer size
31