.st0{fill:#FFFFFF;}

Arduino

Demystifying pointers for Arduino Programming 

 January 18, 2024

By  Peter

Join Our Mailing List

We publish fresh content each week. Read how-to's on Arduino, ESP32, KiCad, Node-RED, drones and more. Listen to interviews. Learn about new tech with our comprehensive reviews. Get discount offers for our courses and books. Interact with our community.

One email per week, no spam, unsubscribe at any time.

If you want to be able to write Arduino programs that go beyond the trivial, sooner or later, you will need to use pointers. Therefore, understanding pointers in C/C++ is imperative for intermediate and advanced-level development.

This article aims to demystify the concept of pointers, illustrating their significance and applications while cautioning against common misuses.

Pointers in C/C++ are a foundational concept. Here’s a definition:

To paraphrase, a pointer is just like any other variable. But, unlike other variables, it holds another variable’s memory address instead of holding a value.

This characteristic is crucial in managing and manipulating data efficiently, especially in systems with limited memory resources, such as the Arduino Uno R3.

Why do we need pointers?

The utility of pointers in Arduino programming is multifaceted.

Primarily, they allow for dynamic memory management. In scenarios where the memory requirement is not fixed or known in advance, pointers enable the allocation and deallocation of memory at runtime, thus optimizing memory usage. This is particularly relevant in the context of the Arduino Uno R3, where efficient memory management is essential due to its limited memory capacity.

Moreover, pointers facilitate the modification of data in functions. By passing the address of a variable to a function (rather than the variable itself), pointers allow functions to modify the original variable’s value directly. This method is memory efficient and enhances the program’s performance by avoiding the overhead of copying large amounts of data.

Let’s drill into some reasons you will want to learn about and use pointers in your Arduino (and other MCU) programs.

Efficient Memory Management

In the constrained environment of Arduino, especially models like the Arduino Uno with limited RAM, efficient memory management is critical. Pointers provide a way to dynamically allocate and deallocate memory, allowing for more flexible and efficient use of the available memory. This is particularly beneficial in applications that require variable amounts of memory at different times or in response to different inputs.

Direct Hardware Access

Arduino programming often involves direct interaction with hardware components. Pointers offer a way to access and manipulate hardware registers directly, leading to faster and more efficient control of hardware peripherals. This is crucial for tasks that require real-time performance and precise hardware control, such as handling sensors or driving motors.

Enhanced Functionality with Functions

Pointers allow passing complex data structures (like arrays and objects) to functions with minimal overhead. Instead of passing a copy of the entire data structure, only the address is passed, saving memory and processing time. This is especially useful in scenarios where functions must modify the actual data passed to them, as pointers enable functions to alter the original data directly.

Flexibility in Data Structures

Pointers are fundamental in creating complex data structures like linked lists, trees, and graphs. These structures are pivotal in advanced programming tasks where data needs to be organized in specific ways for efficient processing and retrieval. The ability to dynamically link elements through pointers offers tremendous flexibility in managing data.

Optimal Use of Limited Resources

Arduino’s limited processing power and memory make optimization a priority. Pointers provide a means to optimize both memory usage and processing time, which is vital for applications that need to run efficiently on the Arduino’s constrained resources. This includes scenarios like handling multiple tasks, processing sensor data, or controlling several peripherals simultaneously.

What are some common risks of using pointers?

However, the power of pointers comes with potential pitfalls.

One common mistake is the misuse of pointers, leading to undefined behavior, such as accessing unallocated or already freed memory locations. This can cause program crashes or erratic behavior.

Additionally, incorrect use of pointers can lead to memory leaks, where allocated memory is not properly freed, eventually exhausting the available memory.

Let’s look at some of the most common problems you will likely encounter when using pointers.

Memory Leaks

One of the most significant risks associated with pointers in Arduino programming is memory leaks. This occurs when memory is allocated using pointers and not properly deallocated. On platforms like the Arduino Uno, where memory is scarce, memory leaks can quickly exhaust the available memory, leading to system crashes or erratic behavior.

For example, if dynamic memory is allocated with malloc() or new and not correctly freed with free() or delete, the allocated memory remains occupied even after it’s no longer needed. Repeated allocations without proper deallocation can deplete the Arduino’s limited memory resources. To read more about malloc() and free(), check out my related blog post.

Dangling Pointers

A dangling pointer arises when a pointer continues to reference a memory location after the memory it points to has been deallocated or freed. Accessing such pointers can lead to undefined behavior, as the memory may be reallocated and used for other purposes.

In Arduino sketches, this can occur if a pointer is used after a function call that frees the memory it points to or if the pointer is not set to NULL after freeing the memory.

Pointer Arithmetic Errors

Pointer arithmetic can be a source of errors in Arduino programming. Incorrect calculations or assumptions about the size of data types can lead to pointers pointing to incorrect memory locations. This can result in overwriting valid data, accessing out-of-bounds memory, and causing system crashes or unpredictable behavior.

For instance, incrementing a pointer without considering the actual size of the data type it points to can lead to accessing unintended memory locations.

Unauthorized Access and Security Risks

Pointers provide low-level memory access, which can be a double-edged sword. If not managed correctly, pointers can be manipulated to access restricted memory areas, potentially leading to security vulnerabilities. This is especially concerning in connected Arduino projects where security is paramount.

Complex Debugging

Debugging pointer-related issues can be challenging due to the indirect nature of pointers. Problems like memory leaks, dangling pointers, or pointer arithmetic errors may not immediately manifest as visible bugs but can cause intermittent and hard-to-diagnose issues, making the debugging process complex and time-consuming.

Are pointers worth the risks?

In short, yes.

You now know that while pointers are an essential tool in Arduino programming for efficient memory management and direct hardware control, they bring potential risks that require careful management. Understanding these pitfalls and employing best practices like proper memory management, cautious use of pointer arithmetic, and thorough debugging is vital for successful Arduino programming with pointers. This careful approach ensures the stability and reliability of applications developed for platforms like the Arduino Uno.

Pointers are a potent tool in the arsenal of an Arduino programmer, offering the ability to manage memory and manipulate data efficiently. However, this power demands a disciplined approach to avoid common errors that can lead to significant issues in program execution. Understanding and applying pointers correctly is crucial for any developer looking to harness the full potential of the Arduino Uno R3 in complex and memory-sensitive applications.

The * pointer operator

In languages like C and C++, the * operator, commonly known as the dereference operator or indirection operator, plays a crucial role. Its primary function is to access or modify the value at the memory address held by a pointer. Let’s break this down for clarity.

Basics of the * Operator

When you have a pointer variable that holds the address of another variable, using the * operator in front of the pointer allows you to access or retrieve the value stored at that memory address.

Example:

int var = 5;
int *ptr = &var;           // ptr holds the address of var
int valueAtPtr = *ptr;  // Dereferencing ptr gives the value of var, which is 5

Similarly, you can use the * operator to modify the value at the location to which the pointer points.

*ptr = 10;  // Changes the value of var to 10

The * Operator in Different Contexts

It’s important to distinguish between the use of * in a declaration and in an expression.

In a declaration, * is used to indicate that a variable is a pointer. In an expression, * is used to dereference the pointer, i.e., to access the value at the address the pointer holds.

Here is an example:

int *ptr;  // Declaration: ptr is a pointer to an int
*ptr = 5;  // Dereferencing: Assigning 5 to the location ptr is pointing to

Also, the * operator can be combined with other operators (like arithmetic or assignment operators) to perform more complex operations. For example, *ptr++ increments the pointer (not the pointed value), whereas (*ptr)++ increments the value pointed to by the pointer. I have included three examples below.

Example 1: Combining with Arithmetic Operators

This example demonstrates incrementing the value pointed to by a pointer and then incrementing the pointer itself.

int main() {
    int a = 5;
    int *p = &a;

    (*p)++;  // Incrementing the value pointed by p
    std::cout << "Value of a after incrementing: " << a << std::endl;  // Outputs 6

    p++;  // Incrementing the pointer itself (points to the next memory location)
}

Example 2: Combining with Assignment Operators

Here, the dereference operator is combined with an assignment operator to modify the value at the pointer’s address.

int main() {
    int x = 10;
    int y = 20;
    int *ptr = &x;

    *ptr *= 2;  // Multiplying the value at ptr (x) by 2
    std::cout << "New value of x: " << x << std::endl;  // Outputs 20

    ptr = &y;  // Point ptr to y
    *ptr += 5;  // Adding 5 to the value at ptr (y)
    std::cout << "New value of y: " << y << std::endl;  // Outputs 25
}

Example 3: Combining with Other Pointers and Operators

This example demonstrates using two pointers together with arithmetic operators.

int main() {
    int a = 4, b = 3;
    int *p1 = &a, *p2 = &b;

    int result = *p1 * *p2;  // Multiplying the values pointed by p1 and p2
    std::cout << "Multiplication of *p1 and *p2: " << result << std::endl;  // Outputs 12

    *p1 = *p2 + 10;  // Assigning the sum of *p2 and 10 to the location p1 points to
    std::cout << "New value of a: " << a << std::endl;  // Outputs 13
}

Example 4: Pointer Arithmetic with Arrays

Pointers can be used to iterate and manipulate arrays efficiently.

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    int *ptr = numbers;  // Point to the first element of the array

    for (int i = 0; i < 5; ++i) {
        *ptr += 5;  // Add 5 to each element of the array
        ptr++;      // Move to the next element
    }

    // Print the modified array
    for (int i = 0; i < 5; ++i) {
        std::cout << numbers[i] << " ";  // Outputs 15, 25, 35, 45, 55
    }
    std::cout << std::endl;
}

The & operator

Now that you know what the “*” operator does, you should take a few minutes to learn about the “&” operator.

The & operator, known as the address-of operator, is used to obtain the memory address of a variable. This operator is fundamental to understanding and working with pointers, as it allows you to access the actual location in memory where a variable is stored.

When you place the & operator before a variable, it yields the memory address where that variable is stored. This address can then be assigned to a pointer, enabling the pointer to refer to that variable. Here is an example:

int var = 10;
int *ptr = &var;  // ptr now holds the address of var

The & is commonly used in pointer initialization or when a function requires the memory address of a variable as an argument. Here is an example:

int main() {
    int num = 5;
    int *p;     // Pointer declaration
    p = &num;   // Assigning address of num to p

    // p now holds the address of num, and *p can access the value of num
    std::cout << "Value of num: " << num << std::endl;         // Outputs 5
    std::cout << "Address of num: " << &num << std::endl;      // Outputs the memory address of num
    std::cout << "Value at address stored in p: " << *p << std::endl;  // Also outputs 5
}

Here’s a few (ahem…) pointers about the & operator:

  • The & operator is essential for creating pointers that point to existing variables. Without it, you could not easily make a pointer refer to the location of a pre-existing variable.
  • It is also used in function arguments to pass the address of a variable (pass by reference) so that the function can modify the original variable.
  • Using the & operator helps in understanding the memory layout of your program, as it provides insight into where variables are stored in memory.

The & operator provides the means to link pointers with variables by giving access to their memory addresses.

OK, enough with the theory. Let’s look at pointers in action.

Pointers in action: Swapping Two Variables Using Pointers

Here’s an Arduino Uno program that demonstrates the correct use of the * (dereference) operator without any external dependencies. This example will use pointers to manipulate and swap the values of two variables.

If you don’t feel like getting your Arduino Uno out to run this program, click here (this will take you to an online Arduino Uno simulator).

void setup() {
    // Initialize serial communication at 9600 bits per second:
    Serial.begin(9600);
    
    int a = 10;
    int b = 20;

    // Print original values
    Serial.print("Original a: ");
    Serial.println(a);
    Serial.print("Original b: ");
    Serial.println(b);

    // Swap values using pointers
    swap(&a, &b);

    // Print swapped values
    Serial.print("Swapped a: ");
    Serial.println(a);
    Serial.print("Swapped b: ");
    Serial.println(b);
}

void loop() {
    // Nothing to do here
}

void swap(int *ptr1, int *ptr2) {
    int temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}

The setup() function initializes serial communication at 9600 bps for output. Two integer variables, a and b, are declared and initialized with values 10 and 20, respectively. Then, the original values of a and b are printed to the Serial Monitor.

Here’s the interesting part:

The swap() function is called with the addresses of a and b. Inside swap(), pointers are used to swap the values of a and b. Yes: instead of using “normal” variables, we are using pointers.

  • The function swap takes two integer pointers as arguments.
  • It uses the dereference operator * to access and swap the values of the two variables pointed to by ptr1 and ptr2.

After the swap, the new values of a and b are printed to the Serial Monitor.

Pointers in action: * and & with an analog sensor

Below is an example of an Arduino Uno program that demonstrates the correct use of the * (dereference) and & (address-of) operators. This program will read an analog value from a potentiometer, use pointers to manipulate the value, and then print both the original and modified values to the Serial Monitor.

Click here to run this program in an online Arduino Uno simulator.

// Define the pin where the potentiometer is connected
const int potentiometerPin = A0;

void setup() {
    // Initialize serial communication at 9600 bits per second:
    Serial.begin(9600);
}

void loop() {
    // Read the input on analog pin 0 (value will be between 0 and 1023)
    int sensorValue = analogRead(potentiometerPin);

    // Print the original sensor value
    Serial.print("Original Sensor Value: ");
    Serial.println(sensorValue);

    // Use a pointer to modify the sensor value
    int *sensorValuePtr = &sensorValue;
    *sensorValuePtr = *sensorValuePtr / 4;  // For example, scale down the value

    // Print the modified sensor value
    Serial.print("Modified Sensor Value: ");
    Serial.println(sensorValue);

    // Wait for a second
    delay(1000);
}

To use this program, connect a potentiometer to your Arduino Uno: one end to 5V, the other to GND, and the wiper to analog pin A0.

The program starts by reading an analog value from a potentiometer connected to pin A0. This value is stored in the variable sensorValue. Then, the original value of sensorValue is printed to the Serial Monitor, and a pointer named sensorValuePtr is declared and initialized with the address of sensorValue using the & operator.

The program then uses the * operator to dereference sensorValuePtr and modify the value of sensorValue. In this example, it scales down the sensor value. The modified value is printed to the Serial Monitor.

The loop repeats after a one-second pause.


Tags

C++, Pointers


You may also like

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

I’m excited to introduce you to the latest and greatest version of KiCad – version 8 (stable release). This article will overview the new features and capabilities added to this popular open-source electronic design automation

Read More
KiCad 8: The new and updated features, a full review

Learning anything worth learning from the ground up is hard. The road to proficiency is filled with roadblocks, distractions, and dead-ends. Everyone learns differently and for different reasons. Some of us learn to advance our

Read More
From struggling to thriving: Understanding the journey from Beginner to Proficient (in programming or anything else)