Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Pointers and Arrays in C: Memory Safety and Multi-dimensional Access

Tech 4

1. Segmentation Faults

When executing C programs, ancountering "segmentation fault" errors can be frustrating due to their vague nature. Here are the common causes and patterns leading to these crashes.

1.1 Wild Pointers

Uninitialized Pointers: Declaring a pointer without assigning a valid address results in undefined behavior when dereferenced.

int *uninitialized;
scanf("%d", uninitialized);  // Dangerous: dereferencing random memory address

Dangling Pointers: After deallocating memory with free(), failing to set the pointer to NULL leaves a "dangling" reference that appears valid but points to reclaimed memory.

int *data_block = (int *)malloc(sizeof(int) * 20);
free(data_block);
// data_block now dangling - Solution: data_block = NULL;

1.2 Memory Leaks and Invalid Access

int *buffer = (int *)malloc(100 * sizeof(int));
buffer = (int *)malloc(200 * sizeof(int));  // Memory leak: first allocation lost

When reassigning a pointer to new memory without freeing the previous allocation, the original memory becomes unreachable. This "garbage memory" remains allocated but inaccessible, wasting system resources.

2. Double Pointers

A double pointer (pointer-to-pointer) stores the memory address of another pointer variable rather than a data variable.

  • Single pointer: Stores the address of a variable
  • Double pointer: Stores the address of a pointer
  • Syntax: storage_type data_type **pointer_variable;

Example declaration: int **meta_ptr;

3. Pointers and Arrays

Array elements can be accessed through two fundamental approaches:

  1. Direct Access: Using the array name (base address) to index elements
  2. Indirect Access: Using a pointer variable that holds the array's address

Key Concept: Array names represent constant base addresses, while pointers are variables that can store and modify addresses.

3.1 Pointers and One-Dimensional Arrays

Direct Access Example: int values[3] = {10, 20, 30};

Address Equivalent Element Value Access
values &values[0] 10 values[0] *values
values+1 &values[1] 20 values[1] *(values+1)
values+2 &values[2] 30 values[2] *(values+2)

Indirect Access: int *cursor = values;

Address Equivalent Element Value Access
cursor &cursor[0] 10 cursor[0] *cursor
cursor+1 &cursor[1] 20 cursor[1] *(cursor+1)
cursor+2 &cursor[2] 30 cursor[2] *(cursor+2)

Critical Distinction: While values and cursor both represent addresses, values is an address constant (cannot use values++), whereas cursor is a modifiable variable (can use cursor++).

Access Patterns for Element at Index i:

  • Values: values[i] or *(values+i)
  • Addresses: &values[i] or values+i
  • Via Pointer: cursor[i] or *(cursor+i)
  • Pointer Addresses: &cursor[i] or cursor+i

3.2 Pointers and Two-Dimensional Arrays

In two-dimensional arrays, pointer arithmetic operates on rows. The array name represents a pointer to the first row, not the first element. To access individual columns, apply the dereference operator * to "downgrade" from a row pointer to an element pointer.

  • matrix: Address of first row (type: int (*)[cols])
  • *matrix: Address of first element, first row (type: int *)
  • (*matrix)+1: Address of first row, second column

Direct Access: int matrix[2][2] = {1, 2, 3, 4};

Address Expression Equivalent Forms Element Value Access Alternative
matrix[0] matrix / *matrix &matrix[0][0] 1 matrix[0][0] / **matrix / *matrix[0]
matrix[0]+1 *matrix+1 &matrix[0][1] 2 matrix[0][1] / *(*matrix+1) / *(matrix[0]+1)
matrix[1] matrix+1 / *(matrix+1) &matrix[1][0] 3 matrix[1][0] / *(*(matrix+1)) / *matrix[1]
matrix[1]+1 *(matrix+1)+1 &matrix[1][1] 4 matrix[1][1] / *(*(matrix+1)+1) / *(matrix[1]+1)

Array Pointers (Row Pointers)

An array pointer is a pointer that points to an entire array (row) rather then a single element.

  • Definition: Pointer to array (row pointer)
  • Syntax: storage_type data_type (*pointer)[column_count];
int grid[2][3] = {1, 2, 3, 4, 5, 6};
int (*row_ptr)[3] = grid;  // Points to array of 3 integers

Both row_ptr and grid behave identically. Incrementing either by 1 advances the address by one full row (3 integer units in this case).

Indirect Access Table (using row_ptr):

Address Expression Equivalant Forms Element Value Access Alternative
row_ptr[0] row_ptr / *row_ptr &row_ptr[0][0] 1 row_ptr[0][0] / **row_ptr / *row_ptr[0]
row_ptr[0]+1 *row_ptr+1 &row_ptr[0][1] 2 row_ptr[0][1] / *(*row_ptr+1) / *(row_ptr[0]+1)
row_ptr[1] row_ptr+1 / *(row_ptr+1) &row_ptr[1][0] 3 row_ptr[1][0] / *(*(row_ptr+1)) / *row_ptr[1]
row_ptr[1]+1 *(row_ptr+1)+1 &row_ptr[1][1] 4 row_ptr[1][1] / *(*(row_ptr+1)+1) / *(row_ptr[1]+1)

Accessing matrix[i][j]:

  • Direct: matrix[i][j] / *(*(matrix+i)+j) / *(matrix[i]+j)
  • Indirect: row_ptr[i][j] / *(*(row_ptr+i)+j) / *(row_ptr[i]+j)

Address of matrix[i][j]:

  • Direct: &matrix[i][j] / *(matrix+i)+j / matrix[i]+j
  • Indirect: &row_ptr[i][j] / *(row_ptr+i)+j / row_ptr[i]+j

3.3 Pointer Arrays

A pointer array is an array whose elements are pointers rather than data values.

  • Definition: Array containing pointer elements
  • Syntax: storage_type data_type *array_name[element_count];

Example: int *ptr_array[3];

Relationship with Double Pointers:

The array name of a pointer array decays to a double pointer. Consider this hierarchy:

double_ptr -> ptr_array -> target_value

Equivalence Relations:

  1. double_ptr = &ptr_array;
  2. *double_ptr = ptr_array;
  3. ptr_array = &target_value;
  4. *ptr_array = target_value;

Substituting (3) into (2): *double_ptr = &target_value

Therefore, dereferencing twice reaches the value: **double_ptr = target_value

Practice Exercise

Problem: Given two sorted character arrays buffer1[10] and buffer2[10], implement a pointer-based solution to merge them into a single sorted output.

Example:

  • Input: buffer1 = "acdgjmno", buffer2 = "befhil"
  • Output: "abcdefghijlmno"
#include <stdio.h>
#include <string.h>

int main(void) {
    char buffer1[20], buffer2[20];
    char *iter1, *iter2;
    
    // Read input strings
    fgets(buffer1, sizeof(buffer1), stdin);
    fgets(buffer2, sizeof(buffer2), stdin);
    
    // Remove trailing newlines
    buffer1[strcspn(buffer1, "\n")] = '\0';
    buffer2[strcspn(buffer2, "\n")] = '\0';
    
    iter1 = buffer1;
    iter2 = buffer2;
    
    // Merge sorted arrays using two-pointer technique
    while (*iter1 != '\0' && *iter2 != '\0') {
        if (*iter1 < *iter2) {
            putchar(*iter1);
            iter1++;
        } else {
            putchar(*iter2);
            iter2++;
        }
    }
    
    // Append remaining elements from either buffer
    if (*iter1 != '\0') {
        printf("%s", iter1);
    }
    if (*iter2 != '\0') {
        printf("%s", iter2);
    }
    
    putchar('\n');
    return 0;
}
Tags: c

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.