Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced C Pointer Concepts: Double Indirection, Array Pointers, and Memory Layout

Tech 1

Double Indirection

Pointer variables themselves occupy memory and possess addresses. A variable storing the address of another pointer creates a double pointer (pointer-to-pointer), enabling multiple levels of indirection.

int value = 42;
int* ptr = &value;      // Single pointer storing value's address
int** dptr = &ptr;      // Double pointer storing ptr's address

Dereferencing operations proceed through levels:

  • *dptr yields ptr (the address of value)
  • **dptr yields value (42)

This concept extends to triple pointers and beyond, though double pointers remain the most common in practical applicasions such as dynamic matrix allocation and function parameter modification.

Array Names and Memory Addresses

The relationship between array identifiers and memory addresses contains subtle distinctions regarding pointer arithmetic. Consider three expressions involving an array:

int data[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};

printf("%p\n", (void*)&data[0]);  // Address of first element
printf("%p\n", (void*)data);       // Array identifier (decays to pointer)
printf("%p\n", (void*)&data);      // Address of entire array structure

While these may display identical hexadecimal values, pointer arithmetic reveals critical behavioral differences:

printf("%p\n", (void*)(&data[0] + 1));  // Advances by sizeof(int)
printf("%p\n", (void*)(data + 1));       // Advances by sizeof(int)
printf("%p\n", (void*)(&data + 1));      // Advances by sizeof(int[10])

The expressions data and &data[0] evaluate to the address of the initial element, whereas &data references the complete contiguous block. Incrementing the latter advances the pointer past the entire array structure (40 bytes for 10 integers).

The sizeof Operator Exception

The sizeof operator provides storage size in bytes. When appplied to an array identifier under specific conditions, it measures the total allocation rather than pointer size:

int values[10] = {0};
printf("%zu\n", sizeof(values));  // Outputs 40 (assuming 4-byte int), not 4 or 8

Two scenarios treat the array name as representing the entire array entity:

  1. sizeof(array_name) when the identifier appears alone without additional operators
  2. &array_name yielding the address of the complete array type

In all other contexts, the identifier undergoes array-to-pointer decay, converting to a pointer to the first element.

Array Element Access Patterns

Both subscript notation and pointer arithmetic access elements equivalently through pointer arithmetic:

int sequence[5] = {5, 10, 15, 20, 25};

// Subscript syntax
for (int idx = 0; idx < 5; idx++) {
    printf("%d ", sequence[idx]);
}

// Equivalent pointer arithmetic
for (int idx = 0; idx < 5; idx++) {
    printf("%d ", *(sequence + idx));
}

For multidimensional structures, the equivalence extends:

int grid[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

// Subscript access: grid[row][col]
// Pointer dereferencing: *(*(grid + row) + col)

Both forms generate identical machine code, demonstrating that arr[i] is syntactic sugar for *(arr+i).

Pointer Arrays

A pointer array stores memory addresses as its elements. Declaration syntax places the subscript operator after the identifier, giving it higher precedence than the indirection operator:

int* int_ptrs[5];      // Array of 5 int pointers
char* char_ptrs[10];   // Array of 10 char pointers
float* float_ptrs[3];  // Array of 3 float pointers

Practical applications include managing separate arrays through a single collection:

int row_a[] = {1, 2, 3};
int row_b[] = {4, 5, 6};
int row_c[] = {7, 8, 9};

int* table[3] = {row_a, row_b, row_c};
printf("%d\n", **table);        // First element of row_a: 1
printf("%d\n", *table[1]);      // First element of row_b: 4
printf("%d\n", table[2][1]);    // Second element of row_c: 8

This construct enables jagged array simulation, though unlike true multidimensional arrays, the memory remains non-contiguous between rows.

Array Pointers

Conversely, a array pointer references an entire array structure rather than individual elements. Parentheses modify precedence to bind the asterisk with the identifier:

int buffer[5] = {100, 200, 300, 400, 500};
int (*array_ref)[5] = &buffer;  // Pointer to array of 5 ints

Dereferencing yields the array itself, which then decays to its first element address:

printf("%p\n", (void*)array_ref);       // Address of buffer
printf("%d\n", **array_ref);            // buffer[0]: 100
printf("%d\n", (*array_ref)[2]);        // buffer[2]: 300
printf("%p\n", (void*)(array_ref + 1)); // Address beyond buffer (next array)

Syntactic Distinction Between Pointer Arrays and Array Pointers

Operator precedence clarifies the distinction between these types:

  • [] binds tighter than *
  • () binds tighter than []

Analysis of declarations:

  • int* list[5]: list binds first to [5], creating an array; the int* specifies element type (pointer array)
  • int(*list)[5]: list binds first to *, creating a pointer; the int[5] specifies target type (array pointer)

String Literals and Constant Pools

String literals reside in read-only memory sections. Modifying them invokes undefined behavior:

char mutable[] = "hello";        // Array copy in stack memory, modifiable
const char* constant = "hello";  // Pointer to string literal pool, immutable

Multiple pointers referencing identical literals may share storage locations:

const char* s1 = "shared_text";
const char* s2 = "shared_text";
// s1 == s2 may evaluate true (implementation-defined optimization)

Conversely, distinct arrays initialized with identical string content occupy separate memory regions.

Array Parameters in Functions

Functions receiving arrays actually receive pointers to the first element. Two parameter syntaxes exist for one-dimensional arrays:

void process_values(int data[], size_t count);
void process_ptrs(int* data, size_t count);

Both declarations accept int collections identically; the brackets serve merely as documentation.

Two-dimensional arrays require explicit column specification or pointer-to-array syntax:

void print_grid(int matrix[][4], size_t rows);
void print_via_ptr(int (*matrix)[4], size_t rows);

The pointer-to-array syntax explicitly indicates each row contains exactly 4 integers, enabling correct pointer arithmetic across row boundaries. The column dimension must remain fixed for the compiler to calculate memory offsets.

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.