Pointers in C Programming: Complete Guide with Examples

Chapter 14: Pointers

Previous Chapter | Current Chapter: Functions | Next Chapter

Author: Er. Mujtaba Ansari

Pointers allow C programs to work directly with memory for faster and more efficient programming.

Real-Life Uses:

Pointers are widely used in operating systems, embedded systems, dynamic memory management, data structures, arrays, and performance-critical applications.

A pointer is a variable that stores the memory address of another variable or object.

A normal variable stores a value, while a pointer stores the address where a value is located in memory.

Example
int x = 10;
int *p;

p = &x;

Here:

  • x is an integer variable that stores 10.
  • &x gives the memory address of x.
  • p is an integer pointer.
  • p stores the address of x.

Conceptually:

x = 10

p
|
|-----> x
        10

Therefore, a simple definition is:

A pointer is a variable used to store the memory address of another variable or object.

Pointers are useful in many areas of C Programming.

They are commonly used to:

  • Access a variable indirectly
  • Modify variables through their addresses
  • Pass data efficiently to functions
  • Work with arrays and strings
  • Access structure members
  • Allocate and manage memory dynamically
  • Build data structures such as linked lists and trees
  • Perform low-level memory operations

A pointer is declared using the * symbol.

Syntax
data_type *pointer_name;
Example
int *p;

Here, p is a pointer that can store the address of an int object.

Other examples:

float *fp;
char *cp;
double *dp;

Here:

  • fp can store the address of a float
  • cp can store the address of a char
  • dp can store the address of a double
Important Note for YOU

Declaring a pointer does not automatically make it point to a valid variable.

For example:

int *p;

At this point, p should not be dereferenced until it is assigned a valid address.

The & operator is called the address-of operator.

It is used to get the memory address of a variable.

Example
int x = 10;

printf("%p", (void *)&x);

Here, &x gives the address of x.

We can store this address in a pointer:

int x = 10;
int *p;

p = &x;

Now, p contains the address of x.

The * symbol is also called the dereference operator when it is used with a pointer expression.

Dereferencing a pointer means accessing the value stored in the object to which the pointer points.

Example
#include <stdio.h>

int main(void)
{
    int x = 9;
    int *p = &x;

    printf("Value of x = %d\n", x);
    printf("Value using pointer = %d\n", *p);

    return 0;
}
Output
Value of x = 9
Value using pointer = 9

Here:

p = &x;

stores the address of x in p.

And:

*p

accesses the value of x.

Suppose:

int x = 9;
int *p = &x;

Then:

&x

means:

Address of x

And:

*p

means:

Value stored in the object pointed to by p

Therefore:

x   → 9
&x  → Address of x
p   → Address of x
*p  → 9

A pointer should be initialized before it is used.

Example
int x = 25;
int *p = &x;

Now p points to x.

We can access the value using:

printf("%d", *p);

Output:

25

A pointer that has not been initialized to a valid address is commonly called a wild pointer.

For example:

int *p;

The pointer has been declared, but it does not point to a valid object that we can safely use.

This is dangerous:

*p = 10;

because the program may access an invalid memory location.

A safer approach is:

int x = 10;
int *p = &x;

Or, if the pointer currently has no target:

int *p = NULL;

A NULL pointer is a pointer that does not currently point to a valid object.

Example:

int *p = NULL;

Before dereferencing a pointer, we can check whether it is NULL.

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

Never dereference a NULL pointer.

A pointer can be used to change the value of the variable it points to.

Example
#include <stdio.h>

int main(void)
{
    int x = 10;
    int *p = &x;

    printf("Before: %d\n", x);

    *p = 25;

    printf("After: %d\n", x);

    return 0;
}
Output
Before: 10
After: 25

The statement:

*p = 25;

changes the value of x because p points to x.

Pointers are very useful when working with functions.

They are commonly used for:

  1. Passing addresses to functions
  2. Modifying original variables inside functions
  3. Returning pointers from functions when the pointed object remains valid

When we pass the address of a variable to a function, the function can modify the original variable through the pointer.

This technique is commonly called call by address.

Important Note for YOU

C does not have true call-by-reference parameters. C always passes arguments by value. When we pass a pointer, the function receives a copy of the address, and that pointer can be used to modify the original object.

The following program swaps two numbers using pointers.

#include <stdio.h>

void swap(int *p, int *q);

int main(void)
{
    int a, b;

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    printf("Before swap: a = %d, b = %d\n", a, b);

    swap(&a, &b);

    printf("After swap: a = %d, b = %d\n", a, b);

    return 0;
}

void swap(int *p, int *q)
{
    int temp;

    temp = *p;
    *p = *q;
    *q = temp;
}
Example Output
Enter two numbers: 5 4
Before swap: a = 5, b = 4
After swap: a = 4, b = 5

Here:

swap(&a, &b);

passes the addresses of a and b.

Inside the function:

*p

and:

*q

are used to access the original variables.

We can also use a pointer as the return value of a function.

#include <stdio.h>

int *largest(int *p, int *q);

int main(void)
{
    int a, b;
    int *big;

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    big = largest(&a, &b);

    printf("Largest number = %d\n", *big);

    return 0;
}

int *largest(int *p, int *q)
{
    if (*p > *q)
        return p;
    else
        return q;
}
Example

Input:

5 9

Output:

Largest number = 9

The function returns the pointer to the larger variable.

Important Point for YOU

Do not return the address of a local variable.

Incorrect:

int *getValue(void)
{
    int x = 10;

    return &x;
}

The local variable x stops existing when the function returns, so returning its address creates an invalid pointer.

Pointers can also be used with structures.

Consider the following structure:

struct Complex
{
    int real;
    int imag;
};

We can create a structure pointer:

struct Complex c;
struct Complex *p = &c;

Now p points to structure c.

The -> operator is used to access structure members through a structure pointer.

For example:

p->real

is equivalent to:

(*p).real

Similarly:

p->imag

is equivalent to:

(*p).imag
#include <stdio.h>

struct Complex
{
    int real;
    int imag;
};

int main(void)
{
    struct Complex a, b, c;

    struct Complex *p = &a;
    struct Complex *q = &b;
    struct Complex *r = &c;

    printf("Enter real and imaginary parts of first number: ");
    scanf("%d %d", &p->real, &p->imag);

    printf("Enter real and imaginary parts of second number: ");
    scanf("%d %d", &q->real, &q->imag);

    r->real = p->real + q->real;
    r->imag = p->imag + q->imag;

    printf("Result = %d + %di\n", r->real, r->imag);

    return 0;
}

Here, the structure members are accessed using the arrow operator.

A structure can also contain a pointer as one of its members.

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

Now:

struct Student student;
int marks = 95;

student.marks = &marks;

The structure member marks is a pointer that stores the address of the variable marks.

We can access the value using:

*student.marks
#include <stdio.h>

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

int main(void)
{
    struct Student student;
    int marks = 95;

    student.roll = 7;
    student.marks = &marks;

    printf("Roll = %d\n", student.roll);
    printf("Marks = %d\n", *student.marks);

    return 0;
}

Pointers and arrays are closely related in C.

Consider:

int a[5] = {20, 8, 91, 12, 9};

The first element is:

a[0]

and its address is:

&a[0]

In most expressions, the array name a is converted to a pointer to its first element.

Therefore:

a

corresponds to:

&a[0]

We can write:

int *p = a;

or:

int *p = &a[0];

Both initialize p to point to the first element.

Important Correction

It is common to hear:

“An array name is a constant pointer.”

This is a useful beginner shortcut, but it is not technically accurate.

An array is not a pointer. In most expressions, the array name decays to a pointer to its first element.

For example:

a = a + 1;

is invalid.

But:

p = p + 1;

is valid when p is a pointer variable.

Suppose:

int a[5] = {20, 8, 91, 12, 9};
int *p = a;

The following expressions can access array elements:

a[0]
*(a + 0)
p[0]
*(p + 0)

All of them access the first element.

Similarly:

a[2]
p[2]
*(a + 2)
*(p + 2)

access the third element.

Pointer arithmetic is commonly used with arrays.

Suppose:

int *p = a;

Then:

p + 1

points to the next int element.

Conceptually:

p       → a[0]
p + 1   → a[1]
p + 2   → a[2]
p + 3   → a[3]
p + 4   → a[4]

Pointer arithmetic is scaled according to the size of the pointed-to type. It does not simply mean adding one byte.

This is an important concept for beginners.

Suppose:

int a[5] = {20, 8, 91, 12, 9};
int *p = a;

Then:

*(p + 4)

accesses the fifth element:

9

But:

*p + 4

means:

20 + 4

which gives:

24

Therefore:

*(p + 4)   // 9
*p + 4     // 24

The parentheses make a major difference.

#include <stdio.h>

int main(void)
{
    int a[100];
    int n;
    int i;
    int largest;
    int *p;

    printf("Enter array size: ");
    scanf("%d", &n);

    if (n <= 0 || n > 100)
    {
        printf("Invalid array size.\n");
        return 1;
    }

    printf("Enter array elements: ");

    for (i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
    }

    p = a;
    largest = *p;

    for (i = 1; i < n; i++)
    {
        if (*(p + i) > largest)
        {
            largest = *(p + i);
        }
    }

    printf("Largest element = %d\n", largest);

    return 0;
}

The following program finds the largest and second-largest distinct elements.

#include <stdio.h>
#include <limits.h>

int main(void)
{
    int a[100];
    int n, i;
    int largest = INT_MIN;
    int secondLargest = INT_MIN;
    int *p = a;

    printf("Enter array size: ");
    scanf("%d", &n);

    if (n < 2 || n > 100)
    {
        printf("Array must contain at least two elements.\n");
        return 1;
    }

    printf("Enter array elements: ");

    for (i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
    }

    for (i = 0; i < n; i++)
    {
        if (*p > largest)
        {
            secondLargest = largest;
            largest = *p;
        }
        else if (*p > secondLargest && *p < largest)
        {
            secondLargest = *p;
        }

        p++;
    }

    if (secondLargest == INT_MIN)
    {
        printf("No distinct second largest element exists.\n");
    }
    else
    {
        printf("Largest element = %d\n", largest);
        printf("Second largest element = %d\n", secondLargest);
    }

    return 0;
}

In C, a string is a sequence of characters terminated by the null character '\0'.

Example:

char city[10] = "Pune";

It is stored conceptually as:

P   u   n   e   \0

We can create a pointer to the first character:

char *p = city;

Now:

printf("%s", p);

prints:

Pune

And:

printf("%c", p[2]);

prints:

n

These two declarations are different:

Character Array

char city[] = "Pune";

This creates a character array that can be modified.

For example:

city[0] = 'D';

is valid.

Pointer to String Literal

const char *city = "Pune";

Here, city points to a string literal. The string should not be modified through this pointer.

Using const clearly indicates that the pointed characters are not intended to be changed.

An array can contain pointers to different strings.

Example:

const char *city[] =
{
    "Gaya",
    "Delhi",
    "Ranchi",
    "Bangalore"
};

Here:

city[0]

points to "Gaya".

city[1]

points to "Delhi".

#include <stdio.h>

int main(void)
{
    const char *city[] =
    {
        "Gaya",
        "Delhi",
        "Ranchi",
        "Bangalore"
    };

    int i;

    for (i = 0; i < 4; i++)
    {
        printf("%s\n", city[i]);
    }

    return 0;
}
Output
Gaya
Delhi
Ranchi
Bangalore

A pointer can also store the address of another pointer.

This is called a pointer to pointer.

Example:

int x = 10;
int *p = &x;
int **q = &p;

The relationship is:

q → p → x

Here:

*p

gives the value of x.

And:

**q

also gives the value of x.

#include <stdio.h>

int main(void)
{
    int x = 10;
    int *p = &x;
    int **q = &p;

    printf("x = %d\n", x);
    printf("*p = %d\n", *p);
    printf("**q = %d\n", **q);

    return 0;
}
Output
x = 10
*p = 10
**q = 10

Pointers are essential for dynamic memory allocation in C.

Dynamic memory can be allocated during program execution using functions such as:

  • malloc()
  • calloc()
  • realloc()
  • free()

These functions are declared in:

#include <stdlib.h>

Example Using malloc()

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

int main(void)
{
    int n;
    int *p;

    printf("Enter number of elements: ");
    scanf("%d", &n);

    if (n <= 0)
    {
        printf("Invalid number of elements.\n");
        return 1;
    }

    p = malloc((size_t)n * sizeof *p);

    if (p == NULL)
    {
        printf("Memory allocation failed.\n");
        return 1;
    }

    for (int i = 0; i < n; i++)
    {
        p[i] = (i + 1) * 10;
    }

    for (int i = 0; i < n; i++)
    {
        printf("%d ", p[i]);
    }

    free(p);

    return 0;
}

Here:

malloc()

allocates memory dynamically.

After using the allocated memory:

free(p);

releases it.

Pointers are powerful, but incorrect use can lead to undefined behavior, crashes, or memory-related bugs.

1. Using an Uninitialized Pointer

Incorrect:

int *p;

*p = 10;

The pointer does not point to a valid object.

Correct:

int x = 10;
int *p = &x;

2. Dereferencing a NULL Pointer

Incorrect:

int *p = NULL;

printf("%d", *p);

A NULL pointer must not be dereferenced.

3. Returning the Address of a Local Variable

Incorrect:

int *getValue(void)
{
    int x = 10;
    return &x;
}

The local variable no longer exists after the function returns.

4. Accessing Memory After free()

Incorrect:

free(p);

printf("%d", *p);

After free(p), the allocated object must no longer be accessed through p.

A common practice is:

free(p);
p = NULL;

when the pointer remains in scope and will not immediately be reused.

5. Going Outside an Array

If:

int a[5];

valid element indexes are:

0 to 4

Accessing:

a[5]

is outside the array and causes undefined behavior.

The same rule applies to pointer arithmetic.

FeatureNormal VariablePointer
StoresA valueAn address
Exampleint x = 10;int *p = &x;
AccessDirectIndirect
Address&xp
Access pointed valueNot applicable*p
Common useStore dataWork with addresses and indirect access
OperatorNamePurpose
&Address-of operatorGets the address of an object
*Dereference operatorAccesses the object pointed to
->Arrow operatorAccesses a structure member through a pointer

Suppose:

int a[5] = {10, 20, 30, 40, 50};
int *p = a;

Then:

a[0]       → 10
*p         → 10
p[0]       → 10
*(p + 0)   → 10

For the third element:

a[2]       → 30
p[2]       → 30
*(a + 2)   → 30
*(p + 2)   → 30

Before moving to advanced C Programming, remember these important points:

  1. A pointer stores an address.
  2. & is used to obtain an object’s address.
  3. * is used to dereference a pointer.
  4. A pointer should be initialized before it is dereferenced.
  5. NULL represents a pointer that does not point to a valid object.
  6. Pointer arithmetic is commonly used with arrays.
  7. p[i] is equivalent to *(p + i) for a pointer p.
  8. Structure pointers commonly use the -> operator.
  9. Pointers allow functions to modify caller-owned objects.
  10. Pointers are essential for dynamic memory allocation.
  11. Invalid pointer operations can cause undefined behavior.
  12. Memory allocated with malloc(), calloc(), or realloc() should eventually be released with free() when it is no longer needed.

What is a pointer in C?

A pointer is a variable that stores the memory address of another variable or object.

What is the use of & in C?

The & operator returns the address of an object.

Example:

int x = 10;
int *p = &x;

What is dereferencing?

Dereferencing means accessing the object stored at the address held by a pointer.

Example:

*p

accesses the value of the object pointed to by p.

What is a wild pointer?

A wild pointer is an uninitialized pointer that has not been assigned a valid address before being used.

What is a NULL pointer?

A NULL pointer is a pointer that does not currently point to a valid object.

Example:

int *p = NULL;

Does C support call by reference?

C uses pass-by-value. However, pointers can be passed to functions so that the function can modify the original object. This technique is commonly called call by address.

What is pointer arithmetic?

Pointer arithmetic allows operations such as incrementing or decrementing a pointer, particularly when working with arrays.

What is a structure pointer?

A structure pointer is a pointer that stores the address of a structure object.

Example:

struct Student s;
struct Student *p = &s;

What is the -> operator?

The -> operator is used to access a structure member through a structure pointer.

Example:

p->roll

Why are pointers important in C?

Pointers are important because they provide direct and indirect access to memory and are widely used with functions, arrays, strings, structures, dynamic memory, and data structures.

Pointers are one of the most powerful and important features of C Programming. A pointer stores the memory address of an object and allows the program to access that object indirectly.

The basic pointer concept can be remembered with this simple example:

int x = 10;
int *p = &x;

printf("%d", *p);

Here:

x   → stores 10
&x  → gives the address of x
p   → stores the address of x
*p  → accesses the value of x

Once you understand this relationship, concepts such as pointer arithmetic, functions, arrays, strings, structures, pointer to pointer, and dynamic memory allocation become much easier to understand.

Pointers are not just an advanced topic in C. They are a fundamental part of the language and form the foundation for many important programming concepts and data structures.



Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top