1

I use the Base64 library for Arduino (but I don't think the problem is related to the library) and have the following function definition:

char* decodeImage(char inputString[], unsigned int inputStringLength) {
  int decodedLength = Base64.decodedLength(inputString, inputStringLength);
  char decodedString[decodedLength];
  Base64.decode(decodedString, inputString, inputStringLength);
  Serial.printf("%s",decodedString); // prints "Base64DecodeExample"
  return decodedString;
}

Later on, I use the following snippet to access the function:

  char inputString[] = "QmFzZTY0RGVjb2RlRXhhbXBsZQA=";
  char* output = decodeImage(inputString, sizeof(inputString));
  Serial.printf("%s",output); // prints garbage

The problem is, that the result of the decoding INSIDE the function seems to be correct, but when I return the value to the calling code, the value get garbled somehow.

I suppose this has something to do with pointer magic or "decaying" of references. Has someone a suggestion?

1 Answer 1

6

Your variable decodedString[] is created inside the function, used inside the function - and destroyed inside the function before it returns.

See how your function decodeImage() returns a char *? That's a pointer to a character array, not the whole array - and you can't return a whole array. The pointer is successfully returned all right - but what it is pointing to is overwritten by future calls and future created variables.

This is the perennial problem of C programmers: how to return something created inside the function. You have four choices (each with their own problems):

  1. You can make the variable global - put it outside the function and let anyone access it. Then you don't even need to return the string - it's already accessible.
  2. You can make decodedString a permanent part of the function by making it static:

    static char decodedString[64];
    

    Note that the above two solutions have the same two problems:
    a. There is only one array. That means that if the same function is called again, it will overwrite the results from last time - use it before you lose it!
    b. You can't calculate the length and then use it - it has to be created at the maximum possible size.

  3. You can make the caller pass in the array to fill. Rather than return the array, have another parameter that is the array to fill - and for safety's sake, get them to pass in the size of the array too. You'll have to check that and return an error if it's not big enough - or do something else sensible.

  4. You can allocate the memory from a different area that where it's currently being allocated from. That area is called the "heap" - it's just a heap o' memory that you can request chunks from - and don't forget to give it back!

To request memory from the heap, you call the function malloc and pass in the number of bytes that you want. It returns a pointer to that lump of memory:

char *decodedString = (char *)malloc(decodedLength);

Note that malloc doesn't know what you're going to use the memory for, so you have to tell the compiler - that's what the (char *) before the call to malloc does.

But if you use malloc, at some stage you must give the memory back with a call to free:

free(returnedString);

The function that did the malloc doesn't have to be the function that frees it - any function can, as long as some function does! Otherwise you'll run out of heap.

Another thing that you must NOT do is call free on the same block twice: that will corrupt the heap, since it will mark the same block on the heap twice, and hand it back out twice later, corrupting the data when it is used again.

EDIT

There's a C++ version of malloc / free called new / delete. You use them similarly:

char *decodedString = new char[decodedLength];
...
// And later...
delete [] returnedString;

It looks and behaves similarly to malloc and free, without the ugly (char *) type-cast. Note that since an array was allocated with the new, you need to call the array version of delete by adding the [] as shown.

2
  • 2
    +1 for option 3. It avoids the bugs of global and disorderly access in option 1; the risks of over-writing of option 2; and the memory fragmentation of option 4. If the caller needs a larger return value than a single value, it provides the memory space for it. Of course if the caller isn't the end-user of the return, but has to return to its caller, the same conditions apply: caller's caller provides the memory, and so on up to the final consumer. Then it up to that consumer function('s author) how to allocate the memory buffer, but that is the one and only place that choice should be made.
    – JRobert
    Commented Jun 29, 2016 at 16:35
  • 2
    ... Coming to embedded systems from other environments, one will have seen all manner of "solutions" to doing this, including returning a local buffer and having gotten away with it (before the system ships!). An embedded system is expected to be able to run 24/7. "Fatal error: out of memory!" is not a option. Neither is stopping or any of the host of failures due to corrupted data. It has to "just work", and option 3 is the least likely to break.
    – JRobert
    Commented Jun 29, 2016 at 16:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.