C Pointers - Complete Guide
Master C pointers including pointer types, arrays, strings, functions, pointer arithmetic, dynamic memory allocation, and programming best practices.
Memory Management
Direct access
Pointer Types
Multiple variations
Performance
Efficient operations
Dynamic Memory
Runtime allocation
Introduction to C Pointers
Pointers are variables that store memory addresses rather than actual values. They are one of the most powerful features of C programming, enabling direct memory access, efficient array handling, dynamic memory allocation, and function callbacks.
Why Pointers Matter
- Direct Memory Access: Manipulate memory directly
- Efficient Arrays: Faster array operations
- Dynamic Memory: Allocate memory at runtime
- Function Arguments: Pass by reference
- Data Structures: Essential for linked lists, trees, graphs
- System Programming: Required for OS and driver development
Pointer Concepts
- Address Operator (&): Gets memory address
- Dereference Operator (*): Accesses value at address
- Pointer Arithmetic: Increment/decrement pointers
- NULL Pointer: Pointer that points to nothing
- Void Pointer: Generic pointer type
- Double Pointer: Pointer to pointer
Fundamental Pointer Concepts
Every variable has: Name (identifier), Value (stored data), Type (data type), and Address (memory location). Pointers store addresses, allowing indirect access to values. Understanding this memory model is crucial for mastering pointers.
C Pointer Types Comparison
Here is a comprehensive comparison of pointer types in C with their key characteristics:
| Pointer Type | Declaration | Use Case | Key Features | Example |
|---|---|---|---|---|
| Basic Pointer | int *ptr; |
Store address of variable | Points to single variable | ptr = &var; |
| Array Pointer | int *ptr; or int (*ptr)[N]; |
Array traversal | Pointer arithmetic works | ptr = arr; |
| String Pointer | char *str; |
String manipulation | Null-terminated | str = "Hello"; |
| Function Pointer | int (*funcPtr)(int, int); |
Callbacks, dynamic dispatch | Points to function | funcPtr = &add; |
| Double Pointer | int **ptr; |
2D arrays, dynamic arrays | Pointer to pointer | ptr = &p; |
| Void Pointer | void *ptr; |
Generic programming | Can point to any type | ptr = &anyVar; |
| NULL Pointer | int *ptr = NULL; |
Initialization, error handling | Points to nothing | if(ptr == NULL) |
Basic Pointers - Declaration and Usage
Basic pointers store the memory address of a variable. They are declared using the asterisk (*) symbol and are fundamental to understanding all pointer concepts.
Syntax:
data_type *pointer_name; // Declaration
pointer_name = &variable_name; // Assignment
*pointer_name = value; // Dereferencing
Key Operators:
- & (Address Operator): Returns memory address of a variable
- * (Dereference Operator): Accesses value at stored address
- NULL: Special value indicating pointer points to nothing
Memory Visualization:
Pointer Relationship with Variables
Basic Pointer Examples:
#include <stdio.h>
int main() {
printf("=== BASIC POINTER OPERATIONS ===\n\n");
// Variable declaration
int number = 42;
float pi = 3.14159;
char letter = 'A';
// Pointer declarations
int *intPtr;
float *floatPtr;
char *charPtr;
// Assign addresses to pointers
intPtr = &number;
floatPtr = π
charPtr = &letter;
// Display addresses and values
printf("Variable 'number':\n");
printf(" Value: %d\n", number);
printf(" Address: %p\n", &number);
printf(" Via pointer: %d\n\n", *intPtr);
printf("Variable 'pi':\n");
printf(" Value: %.5f\n", pi);
printf(" Address: %p\n", &pi);
printf(" Via pointer: %.5f\n\n", *floatPtr);
printf("Variable 'letter':\n");
printf(" Value: %c\n", letter);
printf(" Address: %p\n", &letter);
printf(" Via pointer: %c\n\n", *charPtr);
// Modify values through pointers
printf("=== MODIFYING THROUGH POINTERS ===\n\n");
*intPtr = 100;
*floatPtr = 2.71828;
*charPtr = 'Z';
printf("After modification through pointers:\n");
printf(" number = %d\n", number);
printf(" pi = %.5f\n", pi);
printf(" letter = %c\n\n", letter);
// Pointer to pointer (double pointer)
printf("=== DOUBLE POINTER ===\n\n");
int **doublePtr = &intPtr;
printf("doublePtr = %p (address of intPtr)\n", doublePtr);
printf("*doublePtr = %p (value of intPtr = address of number)\n", *doublePtr);
printf("**doublePtr = %d (value of number)\n", **doublePtr);
// Modify through double pointer
**doublePtr = 999;
printf("After **doublePtr = 999: number = %d\n", number);
return 0;
}
=== BASIC POINTER OPERATIONS ===
Variable 'number':
Value: 42
Address: 0x7ffeed7d8a7c (example)
Via pointer: 42
Variable 'pi':
Value: 3.14159
Address: 0x7ffeed7d8a78 (example)
Via pointer: 3.14159
Variable 'letter':
Value: A
Address: 0x7ffeed7d8a77 (example)
Via pointer: A
=== MODIFYING THROUGH POINTERS ===
After modification through pointers:
number = 100
pi = 2.71828
letter = Z
=== DOUBLE POINTER ===
doublePtr = 0x7ffeed7d8a70 (address of intPtr)
*doublePtr = 0x7ffeed7d8a7c (value of intPtr = address of number)
**doublePtr = 100 (value of number)
After **doublePtr = 999: number = 999
#include <stdio.h>
int main() {
printf("=== NULL POINTERS AND SAFETY ===\n\n");
// Always initialize pointers!
int *uninitializedPtr; // DANGEROUS: contains garbage
int *nullPtr = NULL; // SAFE: explicitly null
int *initializedPtr;
int value = 50;
initializedPtr = &value; // Proper initialization
// Check before dereferencing
printf("1. Checking nullPtr before use:\n");
if(nullPtr == NULL) {
printf(" nullPtr is NULL - safe to initialize\n");
// Can't dereference: *nullPtr would crash
}
printf("\n2. Using initialized pointer:\n");
if(initializedPtr != NULL) {
printf(" Value via pointer: %d\n", *initializedPtr);
}
// Common pointer mistakes
printf("\n3. Common mistakes to avoid:\n");
// Mistake 1: Dereferencing uninitialized pointer
// *uninitializedPtr = 10; // CRASH: Segmentation fault
// Mistake 2: Dereferencing NULL pointer
// *nullPtr = 20; // CRASH: Segmentation fault
// Mistake 3: Using pointer after freeing memory
int *dynamicPtr = (int*)malloc(sizeof(int));
*dynamicPtr = 100;
printf(" Before free: %d\n", *dynamicPtr);
free(dynamicPtr);
// *dynamicPtr = 200; // DANGEROUS: Use after free
// Proper way: Set to NULL after free
dynamicPtr = NULL;
// Void pointer demonstration
printf("\n4. Void pointer (generic pointer):\n");
void *genericPtr;
int intValue = 65;
char charValue = 'B';
float floatValue = 3.14;
genericPtr = &intValue;
printf(" Void pointer pointing to int: %d\n", *(int*)genericPtr);
genericPtr = &charValue;
printf(" Void pointer pointing to char: %c\n", *(char*)genericPtr);
genericPtr = &floatValue;
printf(" Void pointer pointing to float: %.2f\n", *(float*)genericPtr);
// Pointer size
printf("\n5. Pointer sizes (platform dependent):\n");
printf(" Size of int*: %lu bytes\n", sizeof(int*));
printf(" Size of char*: %lu bytes\n", sizeof(char*));
printf(" Size of float*: %lu bytes\n", sizeof(float*));
printf(" Size of void*: %lu bytes\n", sizeof(void*));
return 0;
}
- Always initialize pointers (to NULL or valid address)
- Always check for NULL before dereferencing
- Never dereference freed memory
- Set pointers to NULL after free()
- Be careful with pointer arithmetic
- Use const when pointers shouldn't modify data
Pointer to Arrays - Complete Guide
Array names in C are essentially constant pointers to the first element of the array. Pointers provide efficient ways to traverse and manipulate arrays.
Syntax:
int arr[5]; // Array declaration
int *ptr = arr; // Pointer to array (same as &arr[0])
ptr = &arr[2]; // Pointer to specific element
Key Characteristics:
- Array Name as Pointer:
arris equivalent to&arr[0] - Pointer Arithmetic:
ptr + imoves i elements forward - Indexing:
ptr[i]is same as*(ptr + i) - Multi-dimensional: Pointers can point to 2D/3D arrays
- Bounds Checking: No automatic bounds checking - programmer's responsibility
Memory Visualization:
Array Memory Layout with Pointer
Array Pointer Examples:
#include <stdio.h>
int main() {
printf("=== 1D ARRAY POINTER OPERATIONS ===\n\n");
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
// Different ways to get array address
int *ptr1 = numbers; // Array name decays to pointer
int *ptr2 = &numbers[0]; // Address of first element
int *ptr3 = &numbers[2]; // Address of third element
printf("Array elements: ");
for(int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n\n");
printf("1. Basic pointer operations:\n");
printf(" numbers = %p (address of first element)\n", numbers);
printf(" &numbers[0] = %p (same as above)\n", &numbers[0]);
printf(" *numbers = %d (first element)\n", *numbers);
printf(" *(numbers + 2) = %d (third element)\n", *(numbers + 2));
printf(" numbers[3] = %d (fourth element)\n", numbers[3]);
printf(" 3[numbers] = %d (same as above - unusual but valid)\n", 3[numbers]);
printf("\n2. Pointer arithmetic:\n");
int *ptr = numbers;
printf(" Start: ptr points to %d at address %p\n", *ptr, ptr);
ptr++; // Move to next element
printf(" After ptr++: points to %d at address %p\n", *ptr, ptr);
ptr += 2; // Move two elements forward
printf(" After ptr += 2: points to %d at address %p\n", *ptr, ptr);
ptr--; // Move one element back
printf(" After ptr--: points to %d at address %p\n", *ptr, ptr);
printf("\n3. Array traversal with pointers:\n");
printf(" Forward: ");
for(int *p = numbers; p < numbers + size; p++) {
printf("%d ", *p);
}
printf("\n Backward: ");
for(int *p = numbers + size - 1; p >= numbers; p--) {
printf("%d ", *p);
}
printf("\n\n4. Modifying array through pointer:\n");
int *modPtr = numbers;
*modPtr = 100; // Modify first element
*(modPtr + 3) = 400; // Modify fourth element
printf(" Modified array: ");
for(int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
// Difference between two pointers
printf("\n\n5. Pointer difference:\n");
int *p1 = &numbers[1];
int *p2 = &numbers[4];
printf(" p2 - p1 = %ld (elements between them)\n", p2 - p1);
return 0;
}
#include <stdio.h>
int main() {
printf("=== 2D ARRAYS AND POINTERS ===\n\n");
// 2D array
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("2D Array (3x4 matrix):\n");
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 4; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
printf("\n1. Understanding 2D array memory:\n");
printf(" matrix = %p (address of first row)\n", matrix);
printf(" matrix[0] = %p (address of first element)\n", matrix[0]);
printf(" &matrix[0][0] = %p (same as above)\n", &matrix[0][0]);
printf(" *matrix = %p (address of first element)\n", *matrix);
printf(" **matrix = %d (first element)\n", **matrix);
printf("\n2. Pointer to 2D array (row pointer):\n");
int (*rowPtr)[4] = matrix; // Pointer to array of 4 integers
printf(" First row via rowPtr: ");
for(int i = 0; i < 4; i++) {
printf("%d ", (*rowPtr)[i]);
}
rowPtr++; // Move to next row
printf("\n Second row after rowPtr++: ");
for(int i = 0; i < 4; i++) {
printf("%d ", (*rowPtr)[i]);
}
printf("\n\n3. Element pointer:\n");
int *elemPtr = &matrix[0][0]; // Pointer to first element
printf(" Traversing as 1D array: ");
for(int i = 0; i < 12; i++) {
printf("%d ", *(elemPtr + i));
}
printf("\n\n4. Accessing via pointer arithmetic:\n");
printf(" matrix[1][2] = %d\n", matrix[1][2]);
printf(" *(*(matrix + 1) + 2) = %d (same as above)\n", *(*(matrix + 1) + 2));
printf(" *(matrix[1] + 2) = %d (same as above)\n", *(matrix[1] + 2));
printf("\n5. Dynamic 2D array using pointer to pointer:\n");
int rows = 3, cols = 4;
int **dynamicMatrix = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++) {
dynamicMatrix[i] = (int*)malloc(cols * sizeof(int));
for(int j = 0; j < cols; j++) {
dynamicMatrix[i][j] = (i * cols) + j + 1;
}
}
printf(" Dynamic matrix:\n");
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", dynamicMatrix[i][j]);
}
printf("\n");
}
// Free memory
for(int i = 0; i < rows; i++) {
free(dynamicMatrix[i]);
}
free(dynamicMatrix);
return 0;
}
Pointer to Strings - Complete Guide
In C, strings are arrays of characters terminated by a null character ('\0'). Pointers provide efficient ways to manipulate strings without copying entire strings.
Syntax:
char str[] = "Hello"; // Array of characters
char *ptr = "World"; // Pointer to string literal
char *ptr2 = str; // Pointer to string array
Key Characteristics:
- String Literals: Stored in read-only memory (cannot be modified)
- Null Termination: Strings end with '\0' character
- Array vs Pointer: Arrays can be modified, string literals cannot
- Standard Functions: strcpy, strlen, strcmp work with pointers
- Pointer Arithmetic: Can traverse strings character by character
String Memory Visualization:
String Storage in Memory
String Pointer Examples:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// Custom string functions using pointers
int stringLength(const char *str) {
const char *ptr = str;
while(*ptr != '\0') {
ptr++;
}
return ptr - str;
}
void stringCopy(char *dest, const char *src) {
while((*dest = *src) != '\0') {
dest++;
src++;
}
}
int stringCompare(const char *str1, const char *str2) {
while(*str1 && (*str1 == *str2)) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
int main() {
printf("=== STRING POINTER OPERATIONS ===\n\n");
// Different ways to declare strings
char str1[] = "Hello, World!"; // Modifiable array
char *str2 = "String Literal"; // Read-only literal
char str3[50]; // Empty buffer
const char *str4 = "Constant Pointer"; // Read-only pointer
printf("1. String declarations:\n");
printf(" str1 (array): %s\n", str1);
printf(" str2 (pointer to literal): %s\n", str2);
printf(" str4 (const pointer): %s\n", str4);
// String literals are read-only
// str2[0] = 'X'; // ERROR: Attempt to modify string literal
// But arrays can be modified
str1[0] = 'J';
printf(" After str1[0] = 'J': %s\n", str1);
printf("\n2. Custom string functions:\n");
printf(" Length of '%s': %d\n", str1, stringLength(str1));
stringCopy(str3, str1);
printf(" Copied to str3: %s\n", str3);
printf(" Compare '%s' with '%s': %d\n", str1, str3, stringCompare(str1, str3));
printf(" Compare '%s' with '%s': %d\n", str1, str2, stringCompare(str1, str2));
printf("\n3. Standard library functions:\n");
printf(" strlen(str1): %lu\n", strlen(str1));
strcpy(str3, "New String");
printf(" After strcpy: %s\n", str3);
strcat(str3, " Appended");
printf(" After strcat: %s\n", str3);
printf(" strcmp('%s', '%s'): %d\n", "Apple", "Banana", strcmp("Apple", "Banana"));
printf("\n4. String traversal with pointers:\n");
char *ptr = str1;
printf(" Characters: ");
while(*ptr != '\0') {
printf("%c ", *ptr);
ptr++;
}
printf("\n Uppercase: ");
ptr = str1;
while(*ptr != '\0') {
printf("%c", toupper(*ptr));
ptr++;
}
printf("\n Reverse: ");
ptr = str1 + strlen(str1) - 1; // Point to last character
while(ptr >= str1) {
printf("%c", *ptr);
ptr--;
}
printf("\n\n5. Array of strings (using pointers):\n");
char *fruits[] = {"Apple", "Banana", "Cherry", "Date", "Elderberry"};
int fruitCount = sizeof(fruits) / sizeof(fruits[0]);
printf(" Fruits array:\n");
for(int i = 0; i < fruitCount; i++) {
printf(" [%d] %s (length: %lu)\n", i, fruits[i], strlen(fruits[i]));
}
printf("\n6. Modifying string through pointer:\n");
char buffer[50] = "Original Text";
char *modPtr = buffer;
printf(" Original: %s\n", buffer);
// Modify through pointer
*modPtr = 'M';
modPtr += 9; // Skip to 'T'
*modPtr = 't';
printf(" Modified: %s\n", buffer);
// Find substring
printf("\n7. Finding substring:\n");
char text[] = "The quick brown fox jumps over the lazy dog";
char *found = strstr(text, "fox");
if(found) {
printf(" Found 'fox' at position: %ld\n", found - text);
printf(" Substring: %s\n", found);
}
// Tokenization
printf("\n8. String tokenization:\n");
char data[] = "apple,banana,cherry,date";
char *token = strtok(data, ",");
printf(" Tokens: ");
while(token != NULL) {
printf("'%s' ", token);
token = strtok(NULL, ",");
}
return 0;
}
Function Pointers - Complete Guide
Function pointers store the address of a function, allowing functions to be passed as arguments, returned from functions, and stored in data structures. This enables callbacks and dynamic function dispatch.
Syntax:
return_type (*pointer_name)(parameter_types); // Declaration
pointer_name = &function_name; // Assignment (optional &)
return_value = (*pointer_name)(arguments); // Calling
return_value = pointer_name(arguments); // Alternative calling
Key Characteristics:
- Type Safety: Must match function signature exactly
- Callbacks: Pass functions as arguments to other functions
- Dynamic Dispatch: Choose function at runtime
- Function Tables: Arrays of function pointers for state machines
- qsort: Standard library uses function pointers for comparison
Function Pointer Visualization:
Function Pointer Relationship
Function Pointer Examples:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// Function prototypes
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
// Different function signatures
void printHello() { printf("Hello! "); }
void printWorld() { printf("World! "); }
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }
// Function that takes function pointer as argument
void repeatFunction(void (*func)(), int times) {
for(int i = 0; i < times; i++) {
func();
}
printf("\n");
}
// Comparison function for qsort
int compareInt(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int compareString(const void *a, const void *b) {
return strcmp(*(const char**)a, *(const char**)b);
}
// Function that returns function pointer
int (*getOperation(char op))(int, int) {
switch(op) {
case '+': return &add;
case '-': return &subtract;
case '*': return &multiply;
case '/': return ÷
default: return NULL;
}
}
// Typedef for cleaner syntax
typedef int (*ArithmeticFunc)(int, int);
typedef double (*MathFunc)(double);
int main() {
printf("=== FUNCTION POINTERS DEMO ===\n\n");
printf("1. Basic function pointers:\n");
// Declare function pointer
int (*funcPtr)(int, int);
// Assign and call
funcPtr = &add;
printf(" add(10, 5) = %d\n", funcPtr(10, 5));
funcPtr = &subtract;
printf(" subtract(10, 5) = %d\n", funcPtr(10, 5));
// Alternative syntax (without &)
funcPtr = multiply;
printf(" multiply(10, 5) = %d\n", (*funcPtr)(10, 5));
printf("\n2. Function pointer array (dispatch table):\n");
// Array of function pointers
ArithmeticFunc operations[] = {add, subtract, multiply, divide};
char* opNames[] = {"Add", "Subtract", "Multiply", "Divide"};
int x = 20, y = 4;
for(int i = 0; i < 4; i++) {
printf(" %s(%d, %d) = %d\n", opNames[i], x, y, operations[i](x, y));
}
printf("\n3. Functions as arguments (callbacks):\n");
printf(" Repeating printHello 3 times: ");
repeatFunction(printHello, 3);
printf(" Repeating printWorld 2 times: ");
repeatFunction(printWorld, 2);
// Using with standard library (qsort)
printf("\n4. Using with qsort:\n");
int numbers[] = {42, 17, 89, 5, 33, 71};
int numCount = sizeof(numbers) / sizeof(numbers[0]);
printf(" Original: ");
for(int i = 0; i < numCount; i++) printf("%d ", numbers[i]);
qsort(numbers, numCount, sizeof(int), compareInt);
printf("\n Sorted: ");
for(int i = 0; i < numCount; i++) printf("%d ", numbers[i]);
// String sorting
printf("\n\n5. String sorting with function pointers:\n");
char *words[] = {"banana", "apple", "cherry", "date", "elderberry"};
int wordCount = sizeof(words) / sizeof(words[0]);
printf(" Original: ");
for(int i = 0; i < wordCount; i++) printf("%s ", words[i]);
qsort(words, wordCount, sizeof(char*), compareString);
printf("\n Sorted: ");
for(int i = 0; i < wordCount; i++) printf("%s ", words[i]);
printf("\n\n6. Function returning function pointer:\n");
char ops[] = {'+', '-', '*', '/'};
for(int i = 0; i < 4; i++) {
ArithmeticFunc opFunc = getOperation(ops[i]);
if(opFunc) {
printf(" %d %c %d = %d\n", x, ops[i], y, opFunc(x, y));
}
}
printf("\n7. Using typedef for cleaner syntax:\n");
MathFunc mathOps[] = {square, cube, sqrt, sin};
char* mathNames[] = {"Square", "Cube", "Square Root", "Sine"};
double values[] = {2.0, 3.0, 4.0, 3.14159/2};
for(int i = 0; i < 4; i++) {
printf(" %s(%.2f) = %.4f\n", mathNames[i], values[i], mathOps[i](values[i]));
}
printf("\n8. Advanced: Function pointer in structures:\n");
typedef struct {
char *name;
ArithmeticFunc operation;
} Calculator;
Calculator calc[] = {
{"Addition", add},
{"Subtraction", subtract},
{"Multiplication", multiply},
{"Division", divide}
};
for(int i = 0; i < 4; i++) {
printf(" %s: %d\n", calc[i].name, calc[i].operation(15, 3));
}
printf("\n9. Void pointers with function pointers (advanced):\n");
// Generic operation using void pointers
void (*genericPrint)(const char*) = (void(*)(const char*))puts;
genericPrint(" This is printed via function pointer!");
return 0;
}
Pointers as Function Arguments
Passing pointers as function arguments allows functions to modify variables in the caller's scope (pass-by-reference) and is more efficient for large data structures than copying entire structures.
Syntax:
void function(int *ptr); // Function declaration
function(&variable); // Function call
*ptr = value; // Modify in function
Key Concepts:
- Pass by Reference: Functions can modify original variables
- Efficiency: Avoid copying large structures
- Multiple Returns: Return multiple values via pointers
- Arrays: Arrays are always passed by reference (as pointers)
- Const Correctness: Use const to prevent modification
| Passing Method | Syntax | Effect on Original | Use Case |
|---|---|---|---|
| Pass by Value | void func(int x) |
Original unchanged | Simple values, don't need modification |
| Pass by Reference | void func(int *x) |
Original can be modified | Need to modify, large structures |
| Const Reference | void func(const int *x) |
Original protected | Read-only access, efficiency |
Pointer Arguments Examples:
#include <stdio.h>
#include <string.h>
// Basic swap function
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// Function with const pointer (read-only)
int findMax(const int *arr, int size) {
if(size <= 0) return 0;
int max = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] > max) {
max = arr[i];
}
}
return max;
}
// Function returning multiple values via pointers
void getMinMax(const int *arr, int size, int *min, int *max) {
if(size <= 0) return;
*min = *max = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] < *min) *min = arr[i];
if(arr[i] > *max) *max = arr[i];
}
}
// Modify string through pointer
void toUpperCase(char *str) {
while(*str) {
if(*str >= 'a' && *str <= 'z') {
*str = *str - ('a' - 'A');
}
str++;
}
}
// Array modification
void reverseArray(int *arr, int size) {
int *start = arr;
int *end = arr + size - 1;
while(start < end) {
swap(start, end);
start++;
end--;
}
}
// 2D array processing
void processMatrix(int (*matrix)[3], int rows, int cols) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
matrix[i][j] *= 2; // Double each element
}
}
}
// Function pointer as argument
void applyOperation(int *a, int *b, int (*operation)(int, int)) {
int result = operation(*a, *b);
printf(" Result: %d\n", result);
}
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
printf("=== POINTERS AS FUNCTION ARGUMENTS ===\n\n");
printf("1. Basic pass by reference (swap):\n");
int x = 10, y = 20;
printf(" Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf(" After swap: x = %d, y = %d\n", x, y);
printf("\n2. Array processing with pointers:\n");
int numbers[] = {5, 2, 8, 1, 9, 3};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf(" Original array: ");
for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
int max = findMax(numbers, size);
printf("\n Maximum value: %d\n", max);
printf("\n3. Returning multiple values:\n");
int minVal, maxVal;
getMinMax(numbers, size, &minVal, &maxVal);
printf(" Min: %d, Max: %d\n", minVal, maxVal);
printf("\n4. String modification:\n");
char text[] = "Hello, World!";
printf(" Original: %s\n", text);
toUpperCase(text);
printf(" Uppercase: %s\n", text);
printf("\n5. Array reversal:\n");
printf(" Before reverse: ");
for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
reverseArray(numbers, size);
printf("\n After reverse: ");
for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n\n6. 2D array processing:\n");
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf(" Original matrix:\n");
for(int i = 0; i < 2; i++) {
printf(" ");
for(int j = 0; j < 3; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
processMatrix(matrix, 2, 3);
printf(" After doubling:\n");
for(int i = 0; i < 2; i++) {
printf(" ");
for(int j = 0; j < 3; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
printf("\n7. Function pointer as argument:\n");
int a = 10, b = 5;
printf(" Applying add: ");
applyOperation(&a, &b, add);
printf(" Applying multiply: ");
applyOperation(&a, &b, multiply);
printf("\n8. Pointer to pointer argument:\n");
char *names[] = {"Alice", "Bob", "Charlie"};
char **namePtr = names;
printf(" Names via double pointer:\n");
for(int i = 0; i < 3; i++) {
printf(" %s\n", *(namePtr + i));
}
printf("\n9. Const correctness examples:\n");
// Read-only pointer
const int readOnly = 100;
const int *ptr = &readOnly;
// *ptr = 200; // ERROR: Cannot modify through const pointer
int mutable = 200;
const int *ptr2 = &mutable; // Can point to mutable, but can't modify
printf(" const int*: Can't modify through pointer\n");
printf(" int* const: Can't change pointer itself\n");
printf(" const int* const: Can't do either\n");
return 0;
}
Dynamic Memory Allocation
Dynamic memory allocation allows programs to request memory at runtime using malloc, calloc, realloc, and free. This is essential for data structures that grow/shrink during execution.
Functions:
void* malloc(size_t size); // Allocate memory
void* calloc(size_t num, size_t size); // Allocate and zero
void* realloc(void* ptr, size_t size); // Resize memory
void free(void* ptr); // Free memory
Key Functions Comparison:
| Function | Purpose | Initialization | When to Use |
|---|---|---|---|
malloc() |
Allocate raw memory | Garbage values | General allocation, speed critical |
calloc() |
Allocate and zero | All bits zero | Arrays, need initialization |
realloc() |
Resize allocation | Preserves old data | Growing/shrinking allocations |
free() |
Release memory | N/A | Always when done with memory |
- Always check if malloc/calloc returns NULL
- Always free allocated memory
- Never access freed memory
- Set pointer to NULL after free()
- Don't forget to free in all code paths
- Use sizeof() for portability
Dynamic Memory Examples:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Safe allocation function
void* safeMalloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
return ptr;
}
int main() {
printf("=== DYNAMIC MEMORY ALLOCATION ===\n\n");
printf("1. Basic malloc usage:\n");
// Allocate single integer
int *singleInt = (int*)malloc(sizeof(int));
if(singleInt == NULL) {
printf(" Allocation failed!\n");
return 1;
}
*singleInt = 42;
printf(" Dynamically allocated integer: %d\n", *singleInt);
free(singleInt);
singleInt = NULL; // Good practice
printf("\n2. Dynamic array with malloc:\n");
int arraySize = 5;
int *dynamicArray = (int*)malloc(arraySize * sizeof(int));
if(dynamicArray == NULL) {
printf(" Array allocation failed!\n");
return 1;
}
// Initialize array
for(int i = 0; i < arraySize; i++) {
dynamicArray[i] = i * 10;
}
printf(" Dynamic array: ");
for(int i = 0; i < arraySize; i++) {
printf("%d ", dynamicArray[i]);
}
printf("\n\n3. calloc vs malloc:\n");
// malloc: contains garbage values
int *mallocArray = (int*)malloc(5 * sizeof(int));
printf(" malloc array (garbage values): ");
for(int i = 0; i < 5; i++) {
printf("%d ", mallocArray[i]); // Random values
}
// calloc: initialized to zero
int *callocArray = (int*)calloc(5, sizeof(int));
printf("\n calloc array (zeros): ");
for(int i = 0; i < 5; i++) {
printf("%d ", callocArray[i]); // All zeros
}
free(mallocArray);
free(callocArray);
printf("\n\n4. realloc for resizing:\n");
// Start with small array
int *resizable = (int*)malloc(3 * sizeof(int));
for(int i = 0; i < 3; i++) {
resizable[i] = i + 1;
}
printf(" Original (size 3): ");
for(int i = 0; i < 3; i++) printf("%d ", resizable[i]);
// Resize to larger array
resizable = (int*)realloc(resizable, 6 * sizeof(int));
if(resizable == NULL) {
printf(" Reallocation failed!\n");
free(resizable);
return 1;
}
// Initialize new elements
for(int i = 3; i < 6; i++) {
resizable[i] = (i + 1) * 10;
}
printf("\n After realloc (size 6): ");
for(int i = 0; i < 6; i++) printf("%d ", resizable[i]);
// Shrink array
resizable = (int*)realloc(resizable, 4 * sizeof(int));
printf("\n After shrink (size 4): ");
for(int i = 0; i < 4; i++) printf("%d ", resizable[i]);
free(resizable);
printf("\n\n5. Dynamic 2D array (array of pointers):\n");
int rows = 3, cols = 4;
// Allocate array of pointers (rows)
int **matrix = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++) {
// Allocate each row (columns)
matrix[i] = (int*)malloc(cols * sizeof(int));
for(int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j + 1;
}
}
printf(" Dynamic 2D array (%dx%d):\n", rows, cols);
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
// Free 2D array (must free each row)
for(int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
printf("\n6. Dynamic string allocation:\n");
char *dynamicString = (char*)malloc(50 * sizeof(char));
if(dynamicString) {
strcpy(dynamicString, "This is a dynamically allocated string");
printf(" Dynamic string: %s\n", dynamicString);
// Resize string
dynamicString = (char*)realloc(dynamicString, 100 * sizeof(char));
strcat(dynamicString, " that has been extended!");
printf(" After realloc: %s\n", dynamicString);
free(dynamicString);
}
printf("\n7. Array of dynamic strings:\n");
char **stringArray = (char**)malloc(4 * sizeof(char*));
const char *names[] = {"Alice", "Bob", "Charlie", "Diana"};
for(int i = 0; i < 4; i++) {
// Allocate exact size for each string
stringArray[i] = (char*)malloc((strlen(names[i]) + 1) * sizeof(char));
strcpy(stringArray[i], names[i]);
}
printf(" String array:\n");
for(int i = 0; i < 4; i++) {
printf(" [%d] %s (length: %lu)\n", i, stringArray[i], strlen(stringArray[i]));
}
// Free string array
for(int i = 0; i < 4; i++) {
free(stringArray[i]);
}
free(stringArray);
printf("\n8. Memory leak demonstration (what NOT to do):\n");
int *leaky = (int*)malloc(10 * sizeof(int));
// Forgot to free! This is a memory leak
// Proper way:
int *proper = (int*)malloc(10 * sizeof(int));
// Use proper...
free(proper);
proper = NULL; // Prevent dangling pointer
printf(" Always free allocated memory!\n");
printf("\n9. Using safe allocation wrapper:\n");
int *safeArray = (int*)safeMalloc(5 * sizeof(int));
for(int i = 0; i < 5; i++) {
safeArray[i] = i * 100;
}
printf(" Safe allocation array: ");
for(int i = 0; i < 5; i++) printf("%d ", safeArray[i]);
free(safeArray);
return 0;
}
Common Mistakes and Best Practices
Pointer Best Practices:
- Initialize pointers: Always set to NULL or valid address
- Check for NULL: Before dereferencing any pointer
- Use const: When pointers shouldn't modify data
- Free memory: Always free dynamically allocated memory
- Set to NULL after free: Prevent dangling pointers
- Pointer arithmetic: Be careful with bounds
- Use sizeof: For portability across architectures
- Document ownership: Who allocates, who frees
- Prefer array syntax: ptr[i] over *(ptr + i) for readability
- Validate input: Check sizes before memory operations
Key Takeaways
- Pointers store memory addresses, enabling direct memory access
- & operator gets address, * operator dereferences
- Arrays and pointers are closely related:
arr[i] ≡ *(arr + i) - Strings are character arrays terminated by
'\0' - Function pointers enable callbacks and dynamic dispatch
- Pointers as function arguments allow pass-by-reference
- malloc/calloc allocate memory, free releases it
- Always check if
malloc/callocreturnsNULL - Use
constwith pointers for read-only access - NULL pointers should be checked before dereferencing
- Pointer arithmetic works based on the type size
- Double pointers (
**ptr) are used for 2D arrays and dynamic arrays - Void pointers (
void*) can point to any type but require casting - Memory leaks occur when allocated memory is not freed
- Dangling pointers point to freed memory - always set to NULL after free