The ‘Pointer Maze’ in Embedded Development: Pointer Arrays vs. Array Pointers

A Debugging Story

At 2 AM, Xiao Li was working overtime debugging an IoT sensor project. The device inexplicably restarted after running for a few hours, and the error logs pointed to a memory access exception. After several hours of investigation, he finally pinpointed the source of the problem:

// Problematic code
char *sensor_names[10];     // What is this?
char (*data_buffer)[256];   // And what is this?

Two seemingly similar lines of code, yet they are fundamentally different in memory layout and usage. Xiao Li realized he had fallen into the classic “pointer maze” of C language—the confusion between pointer arrays and array pointers.

Clearing the Fog: The Essence of Different Concepts

Pointer Array (Array of Pointers)

char *sensor_names[10];

Explanation: <span>sensor_names</span> is an array containing 10 elements, each of which is a pointer of type <span>char*</span>.


sensor_names
sensor_names[0]
Hello
sensor_names[1]
World
sensor_names[2]
IoT
...

Memory Layout:

Memory Address     |  Content
0x1000      |  0x2000 (points to "Temperature")
0x1004      |  0x2100 (points to "Humidity")
0x1008      |  0x2200 (points to "Pressure")
...         |  ...

Array Pointer (Pointer to Array)

char (*data_buffer)[256];

Explanation: <span>data_buffer</span> is a pointer that points to an array containing 256 elements of type <span>char</span>.


data_buffer
char[256] array
data[0]
data[1]
data[2]
...
data[255]

Memory Layout:

Memory Address     |  Content
0x1000      |  0x3000 (points to a contiguous array of 256 bytes)

0x3000      |  'H'
0x3001      |  'e'
0x3002      |  'l'
...         |  ...
0x30FF      |  '\0'

Embedded Practice: Showcasing Their Powers

Classic Applications of Pointer Arrays

1. Function Pointer Table (State Machine Implementation)

// Common pattern for embedded state machines
typedef enum {
    STATE_INIT,
    STATE_RUNNING,
    STATE_SLEEP,
    STATE_ERROR
} system_state_t;

// Pointer array: State handling function table
void (*state_handlers[])(void) = {
    handle_init_state,
    handle_running_state,
    handle_sleep_state,
    handle_error_state
};

// Usage: Call the corresponding function based on state
void state_machine_run(system_state_t current_state) {
    state_handlers[current_state]();  // Efficient function call
}

2. Dynamic String Management

// Management of sensor type names
char *sensor_types[] = {
    "Temperature",
    "Humidity",
    "Pressure",
    "Light",
    NULL  // Marks the end
};

// Dynamically add new sensor types
void add_sensor_type(char *new_type) {
    // Each pointer occupies 4/8 bytes (depending on architecture), saving memory
    sensor_types[sensor_count++] = new_type;
}

The Power of Array Pointers

1. Efficient Access to Two-Dimensional Arrays

// Sensor data matrix: 10 sensors, each with 256 historical data points
char sensor_data[10][256];
char (*data_ptr)[256] = sensor_data;  // Array pointer

// Efficient access: Pointer arithmetic is faster than two-dimensional indexing
void process_sensor_data(int sensor_id) {
    char *current_sensor = *(data_ptr + sensor_id);
    // Equivalent to sensor_data[sensor_id], but more efficient after compilation

    for(int i = 0; i < 256; i++) {
        process_data(current_sensor[i]);
    }
}

2. Management of Fixed-Size Buffers

// UART communication buffer
#define BUFFER_SIZE 512
char uart_buffers[4][BUFFER_SIZE];  // 4 UART channels
char (*buffer_ptr)[BUFFER_SIZE];

// Dynamically switch the currently operated buffer
void switch_uart_buffer(int channel) {
    buffer_ptr = &uart_buffers[channel];
    // Now *buffer_ptr is the complete buffer for the current channel
}

Performance and Memory: Let the Data Speak

Feature Pointer Array Array Pointer
Memory Usage Number of pointers × size of pointer + actual data Size of pointer (4/8 bytes)
Access Efficiency Double addressing (pointer → data) Single addressing (offset calculation)
Cache Friendliness Poor (data scattered) Good (contiguous memory)
Dynamicity High (can point to different sized data) Low (fixed array size)

Pitfall Guide: Best Practices

Memory Tricks

// Read declarations from right to left
char *names[10];    // names is an array containing pointers to char
char (*buffer)[10]; // buffer is a pointer pointing to an array of char[10]

Compiler Verification

// Use sizeof for quick verification
printf("Pointer array size: %zu\n", sizeof(char*[10]));     // 40 or 80 bytes
printf("Array pointer size: %zu\n", sizeof(char(*)[10]));   // 4 or 8 bytes

Debugging Tips

// Output differences during GDB debugging
// p names       -> Shows the address of the pointer array
// p *names      -> Shows the value of the first pointer
// p buffer      -> Shows the pointer value pointing to the array
// p *buffer     -> Shows the array content

Conclusion

Returning to the story at the beginning, Xiao Li eventually discovered the problem: he had used an array pointer as a pointer array, leading to memory access violations. In embedded development, such subtle conceptual differences often determine the stability and performance of the system.

Mastering the distinction between pointer arrays and array pointers is not just a matter of syntax; it is a fundamental skill in memory management that every embedded engineer must possess. The next time you encounter a similar “pointer maze,” you will have the tools to navigate it!

💡 Tip: In actual development, it is recommended to use typedef to define aliases for complex pointer types to improve code readability:

typedef char (*BufferPtr)[256];
typedef char *StringArray[10];

Follow us for more embedded development insights! Next issue preview: “In-depth Understanding of Memory Alignment in Embedded Systems”

Leave a Comment