Arduino and memory pointers

Arduino and memory pointers

I needed to use pointers in my Arduino code. The pointer was for a string. So it was a pointer to an array or pointer to a pointer. I had last worked with pointers many decades back, so it needed a bit of remembering and experimenting to get it right. The problem was a little more compounded as it was a  pointer to an array. On the internet there are many documents but none of them actually helped. Finally I had to refer to old textbooks and college materials. Here is a brief for quick reference and recalling pointers.

In classic C strings are arrays. It can be also understood from the declaration of strings in C  – char str[] = “Hello World”;
Now there are two ways to mention/refer the address of an array

  1. &str[0]  — accessing the address of the first location of the array, called as the base address. This format is as per the pointer syntax.
  2. str           — but in C there is a shorter way, just mentioning the name of the array also gives the base address or address of the first location of the array.

There are some advantages of using pointers for strings

  1. We cannot assign a string array to another string array. It will need to be copied – hence the operation will need some time. But with pointers we can just simply assign the address to another pointer.
  2. With pointers we can release the memory if the string is not needed any more. But be aware that to much allocation and releasing can fragment memory on Arduino or the ESP8266. They don’t have garbage collectors and memory compactor. 

 

What I needed was

  1. I will need to update multiple string variables from time to time from the loop() or setup().
  2. The update will take place in another function instead of in loop() or setup(). Why a function? Same operations multiple times with different parameters – nothing fits better than functions. Saves memory also.
  3. I don’t know what the size of the input strings will be. And I didn’t want to have multiple arrays with a huge number of bytes allocated, most of which will not be used most of the time.

Below is the code and explanations

//for simplicity we are using one variable/array here. We are declaring a character pointer that will hold the base address of the array. Malloc for the array will be done in another function and the address to be stored in this variable. Now to store address in this variable we will pass the address of this variable to the function. (we can also return the address from the function and assign in this variable)

char *str1; 

//for easy understanding I am declaring the custom function below loop, 

void setup()
{
   Serial.begin(9600);
   Serial.print("Before: ");
   Serial.println((long int)str1, HEX);
   function2(&str1); // simple - we are passing the address of the pointer variable "str1". 
   Serial.print("After: ");
   Serial.println((long int)str1, HEX); // typecasting with long int will print the address. Without int typecasting it will print the string. 
   
   //print the string. *str1 = str1[0]; *(str1+1) = str1[1]; "str1" holds the base address of the string hence prints the string. *str1 refers to the content of the base address hence 'H'. str1+1 refers to the second location of the array hence *(str1+1) = str1[1]
   Serial.println(str1); 
}

void loop()
{

}

//double pointer because we are referencing to a pointer of pointer or in other words it will hold the address of an address. How? Well, "str1" is a pointer, and we are passing the address of "str1" to this function. 
void function2(char **testLocalFunction2) 
{
   //4 is for storing "Hell" and +1 is for storing the "\0". 
   //sizeof(char) can be omitted as it is 1. But writing it always is a good practice and is necessary when allocating for int, long, structs.... 
  //now point to note is we put the allocated address into *testLocalFunction2. Storing in *testLocalFunction2 is equal to storing in str1 (and not *str1. *str1 is equivalent to **testLocalFunction2). And storing in testLocalFunction2 would have stored the address in the variable testLocalFunction2 which is local to to function2. 
  *testLocalFunction2 = (char *)malloc( sizeof(char) * (4 +1) ); 
  
  Serial.print("Here: ");
  Serial.println((long int)*testLocalFunction2, HEX);  //printing the allocated memory address

  //once we have the memory allocated - we can put the string in various ways. 
// at the simplest we can do a *testLocalFunction2 = "Hell";
// or we can do strcpy(*testLocalFunction2, "Hell");
// and below is the pointer way

  //the array or strings base address is in *testLocalFunction2 so we can do a *testLocalFunction2[0]
  (*testLocalFunction2)[0] = 'H';  

  // *testLocalFunction2[1] is equivalent to this expression. How? *testLocalFunction2 gives the base address, we add 1 to it to get the next address and then use a pointer (*) to that next address to get the content stored in that address, which is E . //Interesting point is adding 1 doesn't mean 1 literally - it is an way to instruct C to get the next address in the array. For char * adding 1 will always fetch the next address but for int it will automatically fetch the next to next address as int is 2 bytes in Arduino or DOS.
  *((*testLocalFunction2)+1) = 'E'; 

  (*testLocalFunction2)[2] = 'l';
  (*testLocalFunction2)[3] = 'l';
  (*testLocalFunction2)[4] = '\0';

}

Output

Before: 0      // printed in setup()
Here: 3FFEF324 // printed in function
After: 3FFEF324 // printed in setup()
HEll            //printed in setup()

 

Below is a diagram to help visualize how things are stored in memory.

I hope this will help someone who is trying to get some idea of pointers to arrays in C.

Some of you might be wondering why I did all these and not access “str1” directly in function2, after all “str1” has been defined as global. Actually my requirement was more complicated and it was not possible to directly access the variable in function2. That is another story for some time else. I didn’t go into that complicated example here, and kept it simple for easy understanding. 

 

 

Leave a Reply