Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

C Pointers: Memory Addressing, Arithmetic, and Function Dispatch

Tech May 19 2

Memory Addressing and Pointer Fundamentals

In C, memory is organized as a contiguous sequence of bytes, each assgined a unique identifier known as a memory address. A pointer is simply a variable designed to store these memory addresses rather than conventional data values like integers or charcaters.

Declaring and Initializing Pointers

The declaration of a pointer specifies the data type of the value it points to, followed by the dereference operator * and the pointer's name. The type informs the compiler how many bytes to read during dereference and how pointer arithmetic should behave.

int value = 50;
int* ptr_value = &value; // Assigning the address of 'value' to the pointer

To interact with pointers, two core operators are used:

  • Address-of operator (&): Retrieves the memory address of a variable.
  • Dereference operator (*): Accesses the value stored at the memory address held by the pointer.
printf("Address: %p\n", ptr_value);
printf("Value: %d\n", *ptr_value); // Outputs 50

Pointer Size and Architecture

The size of a pointer is not determined by the data type it points to, but rather by the system architecture. On a 32-bit system, memory addresses are 32 bits (4 bytes), while 64-bit systems use 64-bit addresses (8 bytes). All pointer types share the same size on a given platform.

printf("Size of char*: %zu\n", sizeof(char*));
printf("Size of int*: %zu\n", sizeof(int*));
printf("Size of double*: %zu\n", sizeof(double*));

Pointer Step Size and Type Significance

The data type of a pointer dictates the step size during pointer arithmetic. When you increment a pointer, its internal address increases by the sizeof its pointed type.

int num = 10;
char* char_ptr = (char*)#
int* int_ptr = #

printf("char_ptr: %p, char_ptr+1: %p\n", char_ptr, char_ptr + 1); // Increments by 1 byte
printf("int_ptr: %p, int_ptr+1: %p\n", int_ptr, int_ptr + 1);   // Increments by 4 bytes

Generic Pointers (void*)

A void* pointer is a generic pointer capable of holding the address of any data type. However, because it lacks type information, you cannot perform pointer arithmetic or direct dereferencing on a void*. It must be cast to a specific type before accessing the underlying data.

float pi = 3.14f;
void* generic_ptr = π

// float value = *generic_ptr; // Error: cannot dereference void*
float value = *(float*)generic_ptr; // Correct: explicit cast

Pointer Arithmetic and Operations

Pointers support three primary arithmetic operations:

Pointer +/- Integer

Adding or subtracting an integer shifts the pointer forward or backward by that multiple of the pointed type's size. This is commonly used for array traversal.

int dataset[5] = {10, 20, 30, 40, 50};
int* cursor = dataset; // Points to the first element

for (int i = 0; i < 5; i++) {
    printf("%d ", *(cursor + i)); // Sequentially prints array elements
}

Pointer - Pointer

Subtracting two pointers yields the number of elements (of the pointed type) that exist between them. Both pointers must point into the same array object.

int calculate_length(const char* str) {
    const char* start = str;
    while (*str != '\0') {
        str++;
    }
    return str - start; // Difference in addresses equals string length
}

Pointer Relational Operations

Pointers can be compared using relational operators (<, >, <=, >=) as long as they point within the same logical array structure.

int elements[6] = {1, 2, 3, 4, 5, 6};
int* end_ptr = elements + 6; // Past-the-end pointer
int* current = elements;

while (current < end_ptr) {
    printf("%d ", *current);
    current++;
}

Invalid Memory Access (Dangling and Wild Pointers)

Pointers that do not point to valid, accessible memory are extremely dangerous. They typically arise from three scenarios:

1. Uninitialized Pointers

A local pointer variable declared without an initializer contains garbage memory addresses.

int* wild_ptr; // Uninitialized
// *wild_ptr = 100; // Undefined behavior: writing to a random memory location

2. Out-of-Bounds Access

Traversing past the allocated boundaries of an array results in accessing unowned memory.

int buffer[4] = {0};
int* idx = buffer;
for (int i = 0; i <= 4; i++) { // Accesses one element past the array
    *(idx + i) = i; 
}

3. Dangling Pointers (Freed Memory)

Returning the address of a local variable or accessing memory after it has been freed.

int* create_value() {
    int local_var = 42;
    return &local_var; // Warning: address of local variable returned
}

Mitigating Invalid Access

Always initialize pointers. If a pointer cannot be immediately assigned a valid address, set it to NULL. Before dereferencing, verify that the pointer is not NULL. The assert macro from <assert.h> is useful for trapping invalid states during development.

#include <assert.h>

void process(int* data) {
    assert(data != NULL); // Terminates program if data is NULL
    *data = 99;
}

Multiple Indirection and Pass-by-Reference

Since pointers are variables themselves, they have memory addresses. A pointer that stores the address of another pointer is called a pointer-to-pointer, or a double pointer.

int quantity = 100;
int* ptr1 = &quantity;
int** ptr2 = &ptr1; // Double pointer

Modifying Caller Variables (Pass-by-Reference)

C passes arguments by value, meaning functions receive local copies. To alter a variable from the caller's scope, pass its address instead.

void reset_status(int* status_ref) {
    *status_ref = 0; // Modifies the original variable
}

int main() {
    int code = 1;
    reset_status(&code); // Pass address
    // code is now 0
    return 0;
}

Function Pointers and Dispatch Tables

Functions reside in memory and have entry point addresses. A function pointer stores this address, allowing functions to be passed as arguments or stored in arrays. A "dispatch table" (or jump table) replaces lengthy switch statements by mapping indices to function pointers.

Dispatch Table Implementation

Consider an arithmetic calculation module. Instead of a sprawling switch block, define an array of function pointers indexed by operation choice.

#include <stdio.h>

typedef int (*MathOp)(int, int);

int add_func(int a, int b) { return a + b; }
int sub_func(int a, int b) { return a - b; }
int mul_func(int a, int b) { return a * b; }
int div_func(int a, int b) { return a / b; }

int main() {
    // Dispatch table: index 0 is unused to match menu choices 1-4
    MathOp operations[] = { NULL, add_func, sub_func, mul_func, div_func };
    
    int choice = 2; // Corresponds to subtraction
    int x = 20, y = 5;
    
    if (choice >= 1 && choice <= 4) {
        int result = operations[choice](x, y);
        printf("Result: %d\n", result); // Outputs 15
    }
    
    return 0;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.