0% found this document useful (0 votes)
17 views18 pages

Pointers in C

The document is a comprehensive guide on pointers in C programming, covering their definition, usage, and importance in memory management, function arguments, and data structures. It includes detailed explanations of pointer declaration, arithmetic, and dynamic memory allocation, along with examples to illustrate concepts. The guide also addresses advanced topics such as pointers to functions and multi-dimensional arrays.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views18 pages

Pointers in C

The document is a comprehensive guide on pointers in C programming, covering their definition, usage, and importance in memory management, function arguments, and data structures. It includes detailed explanations of pointer declaration, arithmetic, and dynamic memory allocation, along with examples to illustrate concepts. The guide also addresses advanced topics such as pointers to functions and multi-dimensional arrays.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Pointers in C

A Complete 8-Chapter Reference


Memory · Allocation · Advanced Techniques

C Programming Language Series


C Programming Language Pointers — A Complete Guide

Chapter 1

Introduction to Pointers

1.1 What is a Pointer?


In the C programming language, a pointer is a variable that stores the memory address of another
variable. Instead of holding a direct value like an integer (e.g., 42) or a character (e.g., 'A'), a pointer
holds the location in memory where that value is stored.

To understand pointers, you must first understand computer memory. The system's RAM (Random
Access Memory) is divided into bytes, each with a unique numeric address. When you declare a variable,
the compiler allocates a block of memory at a specific address to hold its value.

Analogy: Think of memory as a large apartment building. Each apartment has a unique number
(address). A variable is like a resident living in an apartment. A pointer is like a piece of paper that
has the apartment number written on it — it tells you where the resident lives, not the resident
themselves.

1.2 Why Do We Need Pointers?


Pointers are fundamental to C for several reasons:

1. Efficiency: Passing large structures or arrays to functions by copying their entire content is slow
and memory-intensive. Pointers allow us to pass the address of the data, letting the function operate
on the original data directly.
2. Dynamic Memory Allocation: The amount of memory needed by a program is not always known
at compile time. Pointers are essential for allocating memory from the heap at runtime using functions
like malloc(), calloc(), and free().
3. Data Structures: Pointers are the backbone of advanced data structures such as linked lists,
trees, graphs, and hash tables, where elements must reference each other.
4. Hardware Access: In embedded systems and operating systems, pointers are used to access
specific memory-mapped hardware registers.
5. String Manipulation: In C, strings are arrays of characters terminated by a null character (\0).
Pointers provide an efficient way to traverse and manipulate these arrays.

1.3 Memory Addresses


Every variable in C occupies one or more bytes in memory. The address of the first byte is considered the
variable's address. You can obtain the address of a variable using the address-of operator (&).

Example — Printing a variable's address

—2—
C Programming Language Pointers — A Complete Guide

#include <stdio.h>

int main() {
int number = 42;
printf("The value of number is: %d\n", number);
printf("The memory address of number is: %p\n", (void*)&number);
return 0;
}

The %p format specifier prints the address in hexadecimal. The actual address will vary each time you run the
program due to Address Space Layout Randomization (ASLR).

—3—
C Programming Language Pointers — A Complete Guide

Chapter 2

Pointer Declaration and Basic Operators

2.1 Declaring Pointers


A pointer declaration specifies the type of data to which the pointer points. This is crucial because the
compiler needs to know how many bytes to read or write when the pointer is dereferenced.

Syntax:
data_type *pointer_name;

• data_type: Can be int, char, float, double, struct, void, or any other valid type.
• *: The asterisk indicates that this variable is a pointer.
• pointer_name: A valid C identifier.

Pointer declarations
int *ptr_int; // Pointer to an integer
char *ptr_char; // Pointer to a character
float *ptr_float; // Pointer to a float
double *ptr_double; // Pointer to a double
void *ptr_void; // Generic pointer (can hold any address)

Important: The * binds to the variable name, not the type. int* ptr1, num; declares one pointer (ptr1) and one
integer (num).

2.2 The Address-of Operator (&)


The unary operator & returns the memory address of its operand. You cannot take the address of a
constant, a register variable, or a bit-field.

int value = 100;


int *p = &value; // p now holds the address of 'value'

2.3 The Dereference Operator (*)


The unary operator * (when used in a context other than declaration) is the indirection or dereference
operator. It accesses the value stored at the memory address held by the pointer.

Dereferencing a pointer

—4—
C Programming Language Pointers — A Complete Guide

int value = 100;


int *p = &value;

printf("Address stored in p: %p\n", (void*)p);


printf("Value at that address: %d\n", *p); // Dereferencing: prints 100

*p = 200; // Change the value at the address pointed to by p


printf("New value of value: %d\n", value); // Prints 200

2.4 Initializing Pointers


Uninitialized pointers contain garbage addresses. Dereferencing such a pointer leads to undefined
behavior — it might crash, corrupt data, or seem to work sporadically. Always initialize pointers:

1. To the address of an existing variable: int *p = &x;


2. To dynamically allocated memory: int *p = (int*)malloc(sizeof(int));
3. To NULL: A null pointer points to "nowhere" and is safe to check against: int *p = NULL;

—5—
C Programming Language Pointers — A Complete Guide

Chapter 3

Pointers and Functions

3.1 Pass by Value vs. Pass by Reference


C is a pass-by-value language. When you pass a variable to a function, the function receives a copy of its
value. Modifying the parameter inside the function does not affect the original argument.

Pass by Value — Ineffective swap


void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

int main() {
int x = 5, y = 10;
swap(x, y);
printf("x=%d, y=%d\n", x, y); // Output: x=5, y=10 (unchanged!)
return 0;
}

To modify the original variables, you must pass their addresses (using pointers), which simulates
pass-by-reference.

Pass by Reference using Pointers — Correct swap


void swap(int *a, int *b) {
int temp = *a; // temp = value at address a
*a = *b; // value at a = value at b
*b = temp; // value at b = temp
}

int main() {
int x = 5, y = 10;
swap(&x, &y); // Pass addresses of x and y
printf("x=%d, y=%d\n", x, y); // Output: x=10, y=5
return 0;
}

3.2 Pointers as Function Arguments


Passing pointers avoids copying large amounts of data. For large structures, this is far more efficient than
copying the entire struct.

—6—
C Programming Language Pointers — A Complete Guide

typedef struct {
char name[100];
int scores[1000];
} Student;

void processStudent(Student *s) {


// Work directly on the original Student structure
s->scores[0] = 100; // Using arrow operator
}

3.3 Pointers to Functions


A function pointer stores the address of a function, allowing you to call functions indirectly. This enables
callbacks, dynamic dispatch, and generic programming.

Declaration syntax: return_type (*pointer_name)(parameter_types);

Function Pointers — Dynamic Dispatch


#include <stdio.h>

int add(int a, int b) { return a + b; }


int subtract(int a, int b) { return a - b; }

int compute(int (*operation)(int, int), int x, int y) {


return operation(x, y);
}

int main() {
int (*funcPtr)(int, int) = add;
printf("%d\n", compute(funcPtr, 10, 5)); // Output: 15

funcPtr = subtract;
printf("%d\n", compute(funcPtr, 10, 5)); // Output: 5
return 0;
}

—7—
C Programming Language Pointers — A Complete Guide

Chapter 4

Pointers and Arrays

4.1 The Array-Pointer Duality


In C, the name of an array acts as a constant pointer to its first element. For an array int arr[5], arr
is equivalent to &arr[0].

int arr[5] = {10, 20, 30, 40, 50};


int *ptr = arr; // Same as int *ptr = &arr[0];

4.2 Accessing Array Elements with Pointers


You can use pointer arithmetic to traverse arrays. Adding 1 to a pointer moves it forward by
sizeof(data_type) bytes.

Key relationship: arr[i] is equivalent to *(arr + i).

int arr[5] = {10, 20, 30, 40, 50};


int *ptr = arr;

printf("%d\n", ptr[2]); // Output: 30


printf("%d\n", *(ptr + 2)); // Output: 30
printf("%d\n", *(arr + 2)); // Output: 30

4.3 Pointer Arithmetic


Allowed operations on pointers:

• Add/subtract an integer: ptr + n moves n * sizeof(type) bytes forward.


• Subtract two pointers: Returns the number of elements between them (as ptrdiff_t).
• Compare pointers: Using ==, !=, <, >, <=, >= (valid only within the same array).

int arr[] = {1, 2, 3, 4, 5};


int *start = arr;
int *end = &arr[4]; // Points to last element

int length = end - start + 1; // Computes 5


printf("Array length: %d\n", length);

4.4 Arrays of Pointers


You can create arrays where each element is a pointer. This is very useful for storing strings or managing
multiple data structures.

—8—
C Programming Language Pointers — A Complete Guide

const char *fruits[] = {"Apple", "Banana", "Cherry"};


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

4.5 Pointer to an Entire Array


A pointer to an entire array is declared as int (*ptr)[5]. It points to an array of 5 integers.
Incrementing it moves by 5 * sizeof(int) bytes.

int matrix[2][3] = {{1,2,3},{4,5,6}};


int (*rowPtr)[3] = matrix; // Points to the first row (array of 3 ints)

printf("%d\n", (*rowPtr)[1]); // Access first row, second element: 2


rowPtr++; // Now points to second row
printf("%d\n", (*rowPtr)[2]); // Output: 6

—9—
C Programming Language Pointers — A Complete Guide

Chapter 5

Pointers and Strings

5.1 String Representation


In C, a string is a null-terminated character array where \0 marks the end. Pointers are ideal for iterating
through strings efficiently.

5.2 String Literals and Pointers


char *str = "Hello, World!";

str is a pointer to the first character ('H') of a read-only string literal. Attempting to modify str[0]
causes undefined behavior (often a segmentation fault).

5.3 Mutable Strings (Character Arrays)


char str[] = "Hello";
str[0] = 'J'; // Allowed: str is an array on the stack
printf("%s\n", str); // Output: "Jello"

5.4 String Traversal with Pointers


Custom strlen — pointer arithmetic
// Custom strlen using pointer arithmetic
size_t my_strlen(const char *s) {
const char *start = s;
while (*s != '\0') {
s++;
}
return s - start; // Number of characters before '\0'
}

Custom strcpy — compact pointer style


// String copy using pointers
void my_strcpy(char *dest, const char *src) {
while ((*dest++ = *src++) != '\0');
}

5.5 Array of Strings (Pointer to Pointers)


NULL-terminated string array traversal

— 10 —
C Programming Language Pointers — A Complete Guide

const char *colors[] = {"Red", "Green", "Blue", NULL};

// Print all strings using index


for (int i = 0; colors[i] != NULL; i++) {
printf("%s\n", colors[i]);
}

// Using pointer to pointer


const char **ptr = colors;
while (*ptr != NULL) {
printf("%s\n", *ptr);
ptr++;
}

— 11 —
C Programming Language Pointers — A Complete Guide

Chapter 6

Dynamic Memory Allocation

6.1 The Heap


Pointers are the only way to access dynamically allocated memory from the heap. The heap is a pool of
memory available for runtime allocation, as opposed to the stack which holds local variables with
automatic lifetime.

6.2 Memory Management Functions (<stdlib.h>)


Function Purpose

malloc(size_t size) Allocates size bytes of uninitialized memory. Returns NULL on


failure.

calloc(size_t num, size_t size) Allocates num × size bytes and initializes all to zero.

realloc(void *ptr, size_t new_size) Resizes previously allocated memory. May move data to a new
location.

free(void *ptr) Deallocates memory previously allocated by malloc/calloc/realloc.

6.3 Usage Examples


Allocating a single value
// Single integer
int *p = (int*)malloc(sizeof(int));
if (p == NULL) { /* Handle allocation failure */ }
*p = 42;
free(p);
p = NULL; // Good practice to avoid dangling pointer

Dynamic arrays with malloc and calloc


// Dynamic array
int n = 100;
int *arr = (int*)malloc(n * sizeof(int));
if (arr != NULL) {
for (int i = 0; i < n; i++) arr[i] = i;
free(arr);
}

// Using calloc (zero-initialized)


int *arr2 = (int*)calloc(100, sizeof(int));

Growing an array with realloc

— 12 —
C Programming Language Pointers — A Complete Guide

// Using realloc to grow an array


int *arr = (int*)malloc(10 * sizeof(int));
// ... need more space later
int *temp = (int*)realloc(arr, 20 * sizeof(int));
if (temp != NULL) {
arr = temp;
} else {
// realloc failed, original arr remains valid
}

6.4 Common Dynamic Memory Errors


Error Explanation

Memory Leak Forgetting to free() allocated memory. Remains inaccessible until program
ends.

Dangling Pointer Using a pointer after free() has been called on it.

Double Free Calling free() twice on the same pointer — causes undefined behavior.

Buffer Overflow Writing beyond the allocated size — corrupts adjacent memory.

— 13 —
C Programming Language Pointers — A Complete Guide

Chapter 7

Advanced Pointer Topics

7.1 Pointer to Pointer (**)


A pointer to a pointer stores the address of another pointer. This is useful for modifying a pointer inside a
function, or for creating dynamically-sized 2D arrays.

Modifying a pointer inside a function


void allocateMemory(int **ptr, int size) {
*ptr = (int*)malloc(size * sizeof(int));
}

int main() {
int *arr = NULL;
allocateMemory(&arr, 100); // Pass address of pointer
// arr now points to allocated memory
free(arr);
return 0;
}

2D dynamic array — jagged allocation


// 2D dynamic array (jagged)
int rows = 3;
int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc((i+1) * sizeof(int)); // Variable row lengths
}

// Access: matrix[row][col]

// Freeing:
for (int i = 0; i < rows; i++) free(matrix[i]);
free(matrix);

7.2 Constant and Volatile Pointers


There are four combinations with the const keyword:

Declaration Meaning

const int *p; Pointer to constant integer. *p cannot change; p can point elsewhere.

int * const p; Constant pointer to integer. p cannot change; *p can be modified.

const int * const p; Constant pointer to constant integer. Neither p nor *p can change.

— 14 —
C Programming Language Pointers — A Complete Guide

volatile int *p; Pointer to volatile integer — value may change unexpectedly
(hardware registers).

7.3 Pointers to Structures


Access structure members through a pointer using the arrow operator (->), which is shorthand for
(*ptr).member.

struct Point {
int x;
int y;
};

struct Point p1 = {10, 20};


struct Point *ptr = &p1;

ptr->x = 30; // Equivalent to (*ptr).x = 30;


printf("%d\n", ptr->y); // Output: 20

7.4 Generic Pointers (void *)


A void * pointer can hold the address of any data type, but cannot be dereferenced directly without
casting. It is used in generic functions like qsort() and bsearch().

void *generic = malloc(100); // Returns void*


int *int_ptr = (int*)generic; // Cast before dereferencing

— 15 —
C Programming Language Pointers — A Complete Guide

Chapter 8

Best Practices, Pitfalls & Conclusion

8.1 Common Pitfalls


Pitfall Explanation Prevention

Uninitialized pointers Dereferencing garbage address Always initialize to NULL or valid


address

Dangling pointers Using pointer after free() Set pointer to NULL after freeing

Memory leaks Not freeing allocated memory Every malloc needs a matching free

Buffer overflow Writing beyond allocated bounds Respect array bounds; use strncpy
over strcpy

Mismatched types Assigning int* to char* Enable compiler warnings (-Wall


-Wextra)

Double free Calling free() twice on same pointer Set pointer to NULL after freeing

Missing null terminator Treating non-terminated array as string Always ensure \0 at end of char
arrays

8.2 Best Practices Summary


1. Always initialize pointers — int *p = NULL;
2. Check return values of malloc, calloc, realloc for NULL.
3. Free dynamically allocated memory exactly once.
4. Set freed pointers to NULL to avoid dangling references.
5. Use sizeof with the variable not the type: malloc(sizeof(*ptr)).
6. Prefer array indexing over raw pointer arithmetic for readability.
7. Use const to enforce read-only intent and improve safety.
8. Enable compiler warnings — they catch many pointer-related errors.
9. Avoid void* arithmetic — it is non-standard and dangerous.
10. Use tools like Valgrind, AddressSanitizer, and static analyzers.

8.3 Complete Example — Dynamic Stack Implementation


Dynamic Stack — putting it all together

— 16 —
C Programming Language Pointers — A Complete Guide

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

typedef struct {
int *data;
int top;
int capacity;
} Stack;

Stack* createStack(int capacity) {


Stack *s = (Stack*)malloc(sizeof(Stack));
if (!s) return NULL;
s->data = (int*)malloc(capacity * sizeof(int));
if (!s->data) { free(s); return NULL; }
s->top = -1;
s->capacity = capacity;
return s;
}

bool push(Stack *s, int value) {


if (s->top == s->capacity - 1) return false;
s->data[++s->top] = value;
return true;
}

int pop(Stack *s) {


if (s->top == -1) exit(1); // Underflow
return s->data[s->top--];
}

void freeStack(Stack *s) {


if (s) {
free(s->data);
free(s);
}
}

int main() {
Stack *s = createStack(10);
push(s, 5);
push(s, 10);
printf("%d\n", pop(s)); // 10
freeStack(s);
return 0;
}

— 17 —
C Programming Language Pointers — A Complete Guide

8.4 Conclusion
Pointers are arguably the most powerful and distinctive feature of C. They provide direct access to
memory, enable dynamic data structures, and allow for efficient function calls. However, with great power
comes great responsibility.

A solid understanding of pointer syntax, memory layout, and disciplined memory management is essential
for writing correct, efficient, and secure C programs. Mastering pointers separates novice C programmers
from true systems programmers.

Practice with debugging tools such as Valgrind and AddressSanitizer, study existing production-quality
C codebases, and always respect memory boundaries.

End of Document — C Pointers: A Complete Guide

— 18 —

You might also like