Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Pointers in C: Arrays, Sorting, and Multi-Level Pointers

Tech 2

Array Name Interpretation

When working with pointers to access array elements, code like this is common:

int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
int *ptr = &numbers[0];

Here, &numbers[0] obtains the address of the first element. However, the array name itself represents the address of the first element. This can be verified:

#include <stdio.h>
int main() {
    int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%p\n", numbers);
    printf("%p\n", &numbers[0]);
    return 0;
}

Both lines output identical addresses, confirming that numbers equals &numbers[0].

But consider this:

#include <stdio.h>
int main() {
    int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%zu\n", sizeof(numbers));
    return 0;
}

If numbers were merely a pointer, sizeof(numbers) should yield 4 or 8 bytes (depending on platform). Instead, it outputs 40 (10 integers × 4 bytes each). This reveals two exceptions:

  1. sizeof(array_name): When used alone in sizeof, the array name represents the entire array, returning its total size in bytes.
  2. &array_name: Here, the array name represents the whole array, yielding the address of the entire array.

While &numbers[0], numbers, and &numbers may print the same value, their types differ:

#include <stdio.h>
int main() {
    int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("&numbers[0]   = %p\n", &numbers[0]);
    printf("&numbers[0]+1 = %p\n", &numbers[0]+1);
    printf("numbers       = %p\n", numbers);
    printf("numbers+1     = %p\n", numbers+1);
    printf("&numbers      = %p\n", &numbers);
    printf("&numbers+1    = %p\n", &numbers+1);
    return 0;
}

Output (example):

&numbers[0]   = 0x7ffc88
&numbers[0]+1 = 0x7ffc8c  // +4 bytes (int size)
numbers       = 0x7ffc88
numbers+1     = 0x7ffc8c  // +4 bytes
&numbers      = 0x7ffc88
&numbers+1    = 0x7ffcb0  // +40 bytes (entire array size)

Pointer arithmetic respects the pointed-to type. Since &numbers is of type int (*)[10] (pointer to array of 10 ints), adding 1 skips 40 bytes.

Accessing Arrays via Pointers

With this understanding, arrays can be accessed using pointesr:

#include <stdio.h>
int main() {
    int values[10] = {0};
    int count = sizeof(values) / sizeof(values[0]);
    int *ptr = values;
    
    // Input
    for (int idx = 0; idx < count; idx++) {
        scanf("%d", ptr + idx); // Equivalent to values + idx
    }
    
    // Output
    for (int idx = 0; idx < count; idx++) {
        printf("%d ", *(ptr + idx));
    }
    return 0;
}

Since ptr and values are equivalent, ptr[idx] works identically to values[idx]. The expressions *(ptr+idx), *(idx+ptr), values[idx], ptr[idx], and even idx[ptr] are all equivalent. This is because array indexing arr[i] is compiled as *(arr + i).

The Nature of One-Dimensional Array Parameter Passing

Why can't we compute an array's size inside a function that receives it as a parameter?

#include <stdio.h>
void process_array(int arr[]) {
    size_t size_inside = sizeof(arr) / sizeof(arr[0]);
    printf("Inside function: %zu\n", size_inside);
}
int main() {
    int data[10] = {1,2,3,4,5,6,7,8,9,10};
    size_t size_outside = sizeof(data) / sizeof(data[0]);
    printf("Outside function: %zu\n", size_outside);
    process_array(data);
    return 0;
}

Output (64-bit):

Outside function: 10
Inside function: 2  // 8 bytes (pointer) / 4 bytes (int) = 2

Array parameters decay to pointers. When data is passed to process_array, only the address of its first element is transmitted. Thus, sizeof(arr) inside the function yields the size of a pointer (8 bytes on x64), not the array.

Therefore, function signatures for array parameters should ideally use pointer syntax: void process_array(int *arr). Both int arr[] and int *arr are acceptable, but they're semantically identical—both are pointers.

Bubble Sort Implementation

Bubble sort operates by repeatedly comparing adjacent elements and swapping them if they're in the wrong order. Each pass places the next largest element in its correct position.

For an array of N elements:

  • Pass 1: Perform N-1 comparisons, moving the largest element to the end.
  • Pass 2: Perform N-2 comparisons (last element is sorted).
  • Continue until only one element remains unsorted.

Implementation:

void bubble_sort(int *array, int element_count) {
    for (int pass = 0; pass < element_count - 1; pass++) {
        for (int pos = 0; pos < element_count - pass - 1; pos++) {
            if (array[pos] > array[pos + 1]) {
                // Swap
                int temporary = array[pos];
                array[pos] = array[pos + 1];
                array[pos + 1] = temporary;
            }
        }
    }
}

int main() {
    int dataset[] = {2,3,1,4,10,7,5,6,8,9};
    int total = sizeof(dataset) / sizeof(dataset[0]);
    bubble_sort(dataset, total);
    for (int i = 0; i < total; i++) {
        printf("%d ", dataset[i]);
    }
    return 0;
}

Double Pointers

Pointer variables themselves occupy memory and have addressse. A double pointer (pointer-to-pointer) stores the address of a pointer variable.

#include <stdio.h>
int main() {
    int value = 10;
    int *single_ptr = &value;
    int **double_ptr = &single_ptr;

    printf("Address of value:       %p\n", (void*)&value);
    printf("Value of single_ptr:    %p\n", (void*)single_ptr);
    printf("Value of double_ptr:    %p\n", (void*)double_ptr);
    
    // Dereferencing
    printf("*double_ptr == single_ptr: %s\n", 
           *double_ptr == single_ptr ? "true" : "false");
    printf("**double_ptr == value:     %s\n", 
           **double_ptr == value ? "true" : "false");
    return 0;
}

double_ptr points to single_ptr. Dereferencing once (*double_ptr) yields single_ptr (which points to value). Dereferencing twice (**double_ptr) accesses value.

Arrays of Pointers

An array of pointers stores memory addresses as its elements.

int *pointer_array[5];  // Array of 5 integer pointers

Each element can point to an integer or an integer array.

Simulating a Two-Dimensional Array Using Pointer Arrays

Multiple one-dimensional arrays can be grouped via a pointer array to emulate a 2D structure:

int main() {
    int row1[] = {1,2,3,4,5};
    int row2[] = {2,3,4,5,6};
    int row3[] = {3,4,5,6,7};
    
    // Array names are addresses (int*), suitable for a pointer array
    int *matrix[3] = {row1, row2, row3};
    
    // Access like a 2D array
    for (int r = 0; r < 3; r++) {
        for (int c = 0; c < 5; c++) {
            printf("%d ", matrix[r][c]);
        }
        printf("\n");
    }
    return 0;
}

matrix[r] retrieves a pointer to a row array. Adding [c] dereferences to access element c within that row. This structure provides similar syntax to a true 2D array while using separate 1D arrays in memory.

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.