0% found this document useful (0 votes)
2 views31 pages

Module5 Pointers

This document provides an overview of pointers in C programming, covering their fundamentals, declaration, initialization, dereferencing, and operations. It explains the importance of pointers for memory management, efficient parameter passing, and data structures. Additionally, it discusses special types of pointers such as NULL, void, wild, and dangling pointers, along with pointer arithmetic and comparison.

Uploaded by

minakshi.gaonkar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views31 pages

Module5 Pointers

This document provides an overview of pointers in C programming, covering their fundamentals, declaration, initialization, dereferencing, and operations. It explains the importance of pointers for memory management, efficient parameter passing, and data structures. Additionally, it discusses special types of pointers such as NULL, void, wild, and dangling pointers, along with pointer arithmetic and comparison.

Uploaded by

minakshi.gaonkar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

POINTERS 1

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

5.1 Fundamentals of Pointers


A pointer is a special variable in C that stores the memory address of another variable, rather
than storing a data value directly. Every variable in a running program is placed somewhere
in the computer's memory, and every memory location has a unique address. A pointer
“points to” a variable by holding that variable's address.
Pointers are one of the most powerful and, at the same time, most misunderstood features of
the C language. They give the programmer direct control over memory, which is what makes
C suitable for systems programming, embedded development, and performance-critical
applications. This module builds the concept from the ground up: how pointers are declared,
how they are connected to the variables they point to, how arithmetic on addresses works,
how memory can be allocated at run time, and how pointers interact with arrays, strings,
functions, and structures.

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

Example 5.1: Declaring pointers of different types

int *p; // Pointer to int


float *q; // Pointer to float
char *r; // Pointer to char

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).

Why Pointers Matter


 Direct memory access - pointers let a program read or modify the exact byte(s) at a
given address.
 Efficient parameter passing - large structures or arrays can be passed to functions as a
single address instead of copying the whole data, saving both time and stack space.
 Dynamic memory management - pointers are the only way to work with memory
obtained at run time through malloc(), calloc(), and realloc().
 Data structures - linked lists, stacks, queues, trees, and graphs are all built by connecting
nodes together with pointers.
 Call by reference - pointers allow a function to modify the caller's variables directly.
 Array and string handling - arrays and strings in C are closely tied to pointers; the array
name itself behaves like a pointer to the first element.

5.1.2 Address Operator (&)


The address-of operator & is a unary operator that, when applied to a variable, returns the
memory address at which that variable is stored. It is the operator used to obtain the value
that will be placed inside a pointer.
If int a = 5; is declared, then &a evaluates to the address of a. That address can be stored in a
pointer variable, for example int *p = &a;. The compiler decides the exact numeric address;
the programmer never needs to (and should never try to) guess or hard-code it.
Example 5.2: Using the address operator

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

 It cannot be applied to constants: int *p = &50; is a compile-time error.


 It cannot be applied to expressions: int *p = &(a + 1); is a compile-time error, because a +
1 is a temporary value with no fixed address.

int *p = &50; // Error: cannot take address of a constant


int *p = &(a + 1); // Error: cannot take address of an expression

5.2 Declaration, Initialization and Dereferencing of Pointers

4
POINTERS 5

5.2.1 Declaration of a Pointer


A pointer is declared by writing the data type it points to, followed by an asterisk (*) and the
pointer's name. This syntax tells the compiler that the identifier stores a memory address
rather than a direct value of that type.

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.

Common Pointer Declaration Types

 Integer pointer: int *iptr;


 Character pointer: char *cptr;
 Floating-point pointer: float *fptr;
 Structure pointer: struct Node *nodePtr;
Example 5.3: Declaring and printing a pointer's value

#include <stdio.h>

int main()
{
// Normal variable
int var = 10;

// Pointer variable ptr that stores address of var


int *ptr = &var;

// Directly printing ptr gives us an address


printf("%p", ptr);

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

5.2.2 Initializing the Pointer


A pointer is initialized by assigning it the address of a variable using the address operator
(&). Initializing a pointer before it is used ensures that it points to a valid, known memory
location rather than to garbage.
 A pointer can also be initialized to NULL when it is not yet meant to point to any
variable.

pointer_name = &variable;

int *ptr = NULL; // points to nothing, safely

5.2.3 Dereferencing a Pointer


To access the value stored at the address a pointer holds, the pointer must be dereferenced
using the dereference operator (*) - the same symbol used during declaration, but here it
operates on an already-declared pointer rather than naming a new one.
Example 5.4: Dereferencing a pointer to read a value

#include <stdio.h>

int main() {
int var = 10;
int *ptr = &var; // Store address of var

printf("%d", *ptr); // Dereferencing ptr to access the value


return 0;
}

Output: 10

5.2.4 Special Types of Pointers


Four categories of pointers are commonly referred to in C programming, based on what they
point to (or fail to point to) at a given moment.

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));

// After the free() call below, ptr becomes a dangling pointer


free(ptr);
printf("Memory freed\n");

// Removing the dangling state


ptr = NULL;

return 0;
}

Output: Memory freed

Pointer Type Points To Typical Cause / Use

NULL pointer Nothing (deliberately) Explicitly set; safe default state

Void pointer Any data type (generic) Generic functions, malloc() return type

Wild pointer Unknown/garbage address Declared but never initialized

Dangling pointer Freed / invalid memory Used after free() without resetting

8
POINTERS 9

5.3 Operations on Pointers

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.

(a) Address-of Operator (&)


The address-of operator (&) returns the memory address of a variable; that address is then
assigned to a pointer.

pointer = &variable;

Example 5.6: Address-of operator and pointer assignment

#include <stdio.h>

int main()
{
int num = 50;
int *ptr;

ptr = &num;

printf("Value of num = %d\n", num);


printf("Address of num = %p\n", &num);
printf("Pointer value = %p\n", ptr);

return 0;
}

Output: Value of num = 50


Address of num = 6422296
Pointer value = 6422296 (address will vary)
&num gives the address of num, and ptr stores that same address; consequently &num and ptr
always contain the identical memory location.

(b) Assignment Operator (=)


The assignment operator can assign one pointer's value to another pointer of the same type, or
assign NULL to indicate that the pointer currently points to no valid memory location.
Example 5.7: Assigning one pointer to another
9
POINTERS 10

#include <stdio.h>

int main()
{
int x = 100;
int *p1, *p2;

p1 = &x;
p2 = p1;

printf("%d\n", *p2);
return 0;
}

Output: 100

Example 5.8: Assigning NULL

int *ptr = NULL; // Example: assigning NULL

5.3.2 Dereferencing
Dereferencing means accessing (or modifying) the value stored at the address contained in a
pointer, using the dereference operator (*).

*pointer

Example 5.9: Modifying a variable indirectly through a pointer

#include <stdio.h>

int main()
{
int num = 25;
int *ptr = &num;

printf("Value = %d\n", *ptr);

*ptr = 80;
printf("New value = %d\n", num);

return 0;
}

Output: Value = 25
New value = 80
10
POINTERS 11

 ptr stores the address of num.


 *ptr accesses the value stored at that address.
 Modifying *ptr changes num itself, because both refer to the exact same memory
location.

5.3.3 Pointer Arithmetic


Pointer arithmetic allows a pointer to move from one memory location to another. Unlike
arithmetic on ordinary variables, arithmetic on a pointer is scaled by the size of the data type
it points to.
Suppose an integer occupies 4 bytes. If int *ptr = &array[0]; and array starts at address 1000,
then:

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.

(a) Increment (++)


Moves the pointer to the next element.
Example 5.10: Incrementing a pointer

#include <stdio.h>

int main()
{
int arr[3] = {10, 20, 30};

11
POINTERS 12

int *ptr = arr;

printf("%d\n", *ptr);
ptr++;
printf("%d\n", *ptr);

return 0;
}

Output: 10
20

(b) Decrement (--)


Moves the pointer to the previous element.
Example 5.11: Decrementing a pointer

#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

(c) Addition / Subtraction of an Integer


A pointer can move forward or backward by several elements at once by adding or
subtracting an integer.
Example 5.12: Adding an integer offset to a pointer

#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.

(d) Subtraction of Two Pointers


Subtracting one pointer from another (both pointing into the same array) gives the number of
elements between them, not the raw byte difference.
Example 5.13: Subtracting two pointers

#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

5.3.4 Pointer Comparison


Pointers can be compared using relational operators, most commonly while traversing arrays.

(a) Equality (==)


Checks whether two pointers point to the same memory location.

if (p1 == p2)
printf("Same Address");

(b) Not Equal (!=)


Checks whether two pointers refer to different locations.

if (p1 != p2)
printf("Different Address");

13
POINTERS 14

(c) Relational Operators (<, >, <=, >=)


Used to compare addresses, most typically to detect when a pointer has moved past the end of
an array.

while (ptr < arr + 5)


{
printf("%d ", *ptr);
ptr++;
}

(d) NULL Pointer Check


Before dereferencing any pointer, it is good practice to verify that it is not NULL.

if (ptr != NULL)
{
printf("%d", *ptr);
}

This simple check avoids runtime errors such as segmentation faults.

5.3.5 Illegal Pointer Operations


Because pointers represent memory addresses, certain arithmetic operations on them are not
permitted, since the result would have no meaningful interpretation.

1. Adding Two Pointers

int *p1, *p2;


p1 + p2; // Not allowed

Reason: the sum of two memory addresses has no valid meaning.

2. Multiplying Pointers

p1 * p2; // Not allowed

Reason: memory addresses cannot be multiplied.

3. Dividing Pointers

p1 / p2; // Not allowed

Reason: dividing memory addresses is meaningless.

14
POINTERS 15

4. Bitwise Operations on Pointers

p1 & p2;
p1 | p2; // Not allowed

Reason: bitwise manipulation of pointer addresses is not permitted in standard C.

5. Adding a Floating-Point Number

ptr = ptr + 2.5; // 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

5.4 Concept of Dynamic Memory Allocation

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

malloc() Allocates a single block of memory

calloc() Allocates multiple blocks and initializes them to zero

realloc() Resizes previously allocated memory

free() Releases allocated memory back to the system

5.4.1 malloc() Function


malloc() (memory allocation) allocates the specified number of bytes from the heap and
returns a void pointer to the first byte of the allocated block, or NULL if the allocation fails.
The allocated memory is not initialized - it contains garbage values until the program writes
to it.

ptr = (datatype *)malloc(number_of_elements * sizeof(datatype));

Example 5.14: Allocating an array of 5 integers with malloc()

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;
}

5.4.2 calloc() Function


calloc() (contiguous allocation) allocates memory for a specified number of elements and
automatically initializes every byte of that memory to zero, unlike malloc().

ptr = (datatype *)calloc(number_of_elements, sizeof(datatype));

Example 5.15: Allocating a zero-initialized array with calloc()

#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

for (int i = 0; i < 5; i++)


printf("%d ", ptr[i]);

free(ptr);
return 0;
}

Output: 0 0 0 0 0

5.4.3 realloc() Function


realloc() changes the size of a block of memory that was previously allocated with malloc(),
calloc(), or realloc() itself. It can grow or shrink the block, and - where possible - preserves
the existing data up to the smaller of the old and new sizes.

ptr = (datatype *)realloc(ptr, new_size);

Example 5.16: Resizing a previously allocated block with realloc()

#include <stdio.h>
#include <stdlib.h>

int main()
{
int *ptr;
ptr = (int *)malloc(3 * sizeof(int));
ptr = (int *)realloc(ptr, 6 * sizeof(int));

printf("Memory resized successfully");


free(ptr);
return 0;
}

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.

5.4.4 free() Function


free() releases dynamically allocated memory, returning it to the operating system (or the
runtime's memory manager) so it can be reused. Every successful malloc(), calloc(), or
realloc() call should eventually be matched with exactly one free() call.

18
POINTERS 19

free(ptr);

Example 5.17: Releasing memory and clearing the pointer

#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;
}

Why Assign NULL After free()?


After free(ptr) executes, ptr still contains the old address - it has become a dangling pointer
(section 5.2.4). Assigning NULL immediately afterwards prevents any accidental later access
to memory that no longer belongs to the program.

free(ptr);
ptr = NULL;

19
POINTERS 20

Figure 5.3: The dynamic memory allocation lifecycle, from request to release.

Function Initializes Memory? Can Resize? Typical Use

malloc() No (garbage values) No Fast allocation when


initial values will be
written immediately

calloc() Yes (all zero) No Allocating arrays that


must start at zero, e.g.
counters

realloc() Preserves old data Yes Growing/shrinking an


array whose size was not
known upfront

free() N/A N/A Releasing memory once


it is no longer needed

20
POINTERS 21

5.5 Pointers and Arrays


Arrays and pointers are closely related in C. The name of an array, when used in most
expressions, decays into a pointer to its first element. This is why array elements can be
accessed using either array-indexing syntax or pointer arithmetic - the two are, in fact,
equivalent at the machine level.
Example 5.18: Accessing array elements through pointer arithmetic

#include <stdio.h>

int main()
{
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // same as ptr = &arr[0];

for (int i = 0; i < 5; i++)


printf("%d ", *(ptr + i)); // equivalent to arr[i]

return 0;
}

Output: 10 20 30 40 50

Expression Meaning

arr Address of the first element (same as &arr[0])

*arr Value of the first element (same as arr[0])

arr + i Address of the i-th element

*(arr + i) Value of the i-th element (same as arr[i])

&arr[i] Address of the i-th element (same as arr + i)

5.5.1 Pointers and Two-Dimensional Arrays


A two-dimensional array is stored in memory as a contiguous block, row after row. A pointer
to such an array must therefore be declared as a pointer to an array of a given width, so that
pointer arithmetic advances by one entire row at a time.

21
POINTERS 22

Example 5.19: Traversing a 2-D array using a row pointer

#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)

for (int i = 0; i < 2; i++)


for (int j = 0; j < 3; j++)
printf("%d ", *(*(p + i) + j));

return 0;
}

Output: 1 2 3 4 5 6

22
POINTERS 23

5.6 Pointers and Strings


In C, a string is simply an array of characters terminated by the null character '\0'. Because
arrays and pointers are closely related, strings are very commonly handled through character
pointers.
Example 5.20: Printing a string character by character using a pointer

#include <stdio.h>

int main()
{
char str[] = "Hello";
char *p = str;

while (*p != '\0')


{
printf("%c", *p);
p++;
}

return 0;
}

Output: Hello

Character Array vs Character Pointer

Aspect char str[] = "Hello"; char *p = "Hello";

Storage Modifiable array on the stack Pointer to a (usually read-only)


string literal

Can modify Yes No - undefined behaviour if


contents? attempted

sizeof Size of the whole array (6 bytes Size of a pointer (typically 4 or


here) 8 bytes)

Reassignment str = otherArray; is illegal p = otherString; is legal

5.6.1 Common String-Handling Functions and Pointers

Function Description

23
POINTERS 24

strlen(s) Returns the length of the string s (excluding the null


terminator)

strcpy(dest, src) Copies the string src into dest

strcat(dest, src) Appends src to the end of dest

strcmp(s1, s2) Compares two strings lexicographically

24
POINTERS 25

5.7 Pointer to Pointer (Double Pointer)

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

printf("Value of var = %d\n", var);


printf("Value via *p = %d\n", *p);
printf("Value via **pp = %d\n", **pp);

return 0;
}

Output: Value of var = 25


Value via *p = 25
Value via **pp = 25
 *pp gives the value stored in pp, which is the address of var - i.e., it equals p.
 **pp dereferences that address again, giving the value of var itself.
Double pointers are used in situations such as modifying a pointer itself from within a
function (since a function can only change the caller's data through a pointer to it),
dynamically allocating two-dimensional arrays, and building arrays of strings.

25
POINTERS 26

5.8 Array of Pointers

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];

Example 5.22: An array of character pointers (array of strings)

#include <stdio.h>

int main()
{
char *names[3] = {"Amit", "Priya", "Rahul"};

for (int i = 0; i < 3; i++)


printf("%s\n", names[i]);

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

5.9 Pointers and Functions

5.9.1 Call by Value vs Call by Reference


By default, C passes arguments to functions by value - the function receives a copy of the
argument, so changes made inside the function do not affect the caller's original variable.
Passing a pointer instead allows the function to receive the address of the caller's variable, so
it can modify the original value directly. This technique is called call by reference (simulated
in C through pointers, since C has no native reference type).

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
}

void modifyByReference(int *x)


{
*x = 20; // the caller's variable itself changes
}

int main()
{
int a = 10;

modifyByValue(a);
printf("After call by value: %d\n", a);

modifyByReference(&a);

27
POINTERS 28

printf("After call by reference: %d\n", a);

return 0;
}

Output: After call by value: 10


After call by reference: 20

5.9.2 Passing Arrays to Functions


When an array is passed to a function, what is actually passed is a pointer to its first element -
the entire array is not copied. Consequently, changes made to array elements inside the
function are reflected in the caller's original array.
Example 5.24: Passing an array to a function

#include <stdio.h>

void doubleValues(int arr[], int n)


{
for (int i = 0; i < n; i++)
arr[i] = arr[i] * 2;
}

int main()
{
int nums[4] = {1, 2, 3, 4};
doubleValues(nums, 4);

for (int i = 0; i < 4; i++)


printf("%d ", nums[i]);

return 0;
}

Output: 2 4 6 8

5.9.3 Function Pointers


A function pointer stores the address of a function rather than the address of a data variable.
Because a function's address is fixed once the program is compiled, a function pointer can be
used to call that function indirectly - a technique used to implement callbacks, dispatch
tables, and plug-in style designs.

return_type (*pointer_name)(parameter_types);

28
POINTERS 29

Example 5.25: Declaring and calling through a function pointer

#include <stdio.h>

int add(int a, int b)


{
return a + b;
}

int main()
{
int (*funcPtr)(int, int) = add;

printf("%d\n", funcPtr(5, 3)); // calls add(5, 3) indirectly


return 0;
}

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.

5.10 Pointers and Structures


A pointer can also point to a structure. Structure pointers are used heavily in dynamic data
structures such as linked lists, where each node needs to reference the next node.
Example 5.26: Accessing structure members through a pointer using the arrow operator

#include <stdio.h>

struct Student
{
char name[20];
int roll;
};

int main()
{
struct Student s1 = {"Anita", 101};
struct Student *sp = &s1;

printf("Name: %s\n", sp->name); // arrow operator


printf("Roll: %d\n", sp->roll);

return 0;
}

29
POINTERS 30

Output: Name: Anita


Roll: 101

Access Method Syntax Used When

Dot operator [Link] Working directly with a structure


variable

Arrow operator sp->name Working through a pointer to a structure

Explicit dereference (*sp).name Equivalent to sp->name, rarely used


because it is less readable

5.10.1 Self-Referential Structures and Linked Lists


A structure that contains a pointer to another structure of the same type is called self-
referential. This is the foundation of the singly linked list, one of the most important dynamic
data structures.

Example 5.27: A self-referential structure defining a linked-list node

struct Node
{
int data;
struct Node *next; // pointer to the next node
};

5.11 Common Pointer Errors and Best Practices

5.11.1 Common Errors


 Dereferencing an uninitialized pointer - reading or writing through a wild pointer
accesses unpredictable memory.
 Dereferencing a NULL pointer - attempting *ptr when ptr is NULL crashes most
programs with a segmentation fault.
 Using a dangling pointer - accessing memory through a pointer after it has been freed.
 Memory leaks - forgetting to call free() on memory that is no longer needed.
 Buffer overflow via pointer arithmetic - advancing a pointer beyond the bounds of the
array it points into.
 Type mismatch - assigning the address of one type to an incompatible pointer type
without an explicit, correct cast.

30
POINTERS 31

 Double free - calling free() twice on the same pointer, which corrupts the memory
manager's internal bookkeeping.

5.11.2 Best Practices


 Always initialize a pointer, either with a valid address or with NULL.
 Check the return value of malloc(), calloc(), and realloc() for NULL before using the
pointer.
 Set a pointer to NULL immediately after calling free() on it.
 Match every successful allocation with exactly one free() call.
 Keep pointer arithmetic within the bounds of the array or block it refers to.
 Prefer sizeof(*ptr) over sizeof(type) in allocation calls, since it automatically stays correct
even if the pointer's type is later changed.

ptr = malloc(n * sizeof(*ptr)); // safer than sizeof(int), stays correct if type changes

5.12 Advantages and Disadvantages of Pointers

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

You might also like