1

I am trying to write my own Arduino library and I would like to make it useful not just for ESP32/ESP8266 boards but also with Arduino boards.

I have created my header file like this

test.cpp

#ifndef TEST
#define TEST
#include "Arduino.h"

#define MAX_STRING_LEN 50

class MyClass{
    private:

    public:
        String getString();
        char * getCharPointer();
        char * getAnotherCharPointer();
};

#endif

and my

test.cpp

#include "test.h"


static char buffer[MAX_STRING_LEN+1];

String MyClass::getString(){
    // Sample REST API call
    String testString = String("{\"test:\" \"hello String json\"}");

    return testString;
}

char * MyClass::getCharPointer(){
    // Sample REST API call
    const char * sample_json = "{\"test:\" \"hello Char Pointer json\"}";
    strcpy(buffer, sample_json);
    return buffer;
}

char * MyClass::getAnotherCharPointer(){
    // Sample REST API call
    const char * sample_json = "{\"test:\" \"hello Another Char Pointer json\"}";
    strcpy(buffer, sample_json);
    return buffer;
}

My test file main.cpp

main.cpp

#include <Arduino.h>
#include "test.h"

MyClass myClass;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  Serial.println(myClass.getString());
  Serial.println(myClass.getCharPointer());
  Serial.println(myClass.getAnotherCharPointer());
}

void loop() {
  // put your main code here, to run repeatedly:
}

Result is ok

{"test:" "hello String json"}
{"test:" "hello Char Pointer json"}
{"test:" "hello Another Char Pointer json"}

I have these doubts that I would like to ask for suggestions.

  1. Is returning the String class in Header files, not a good idea? For ESP32/ESP8266 they are okay as they have big memory but for Arduino Uno boards I see a lot of discussions about not using String because it is bad and causes memory fragmentation etc.
  2. Is returning char * the safest way to make it compatible and useful to all MCU boards?
  3. Next question in char pointer, I often see this pattern done when returning char pointers like in this Time.cpp wherein a static buffer is used and defined at the implementation file (test.cpp) and all functions(getCharPointer, getAnotherCharPointer) that needs to return a char pointer just manipulates the static char buffer by using strcpy. Is this the proper way to go in Arduino programming?

Can somebody please clear my doubts? Thank you.

3
  • 1
    Good question! I am afraid it may be a bit opinion based though. I like the static buffer approach for its memory friendliness. It is, however, dangerous unless the library user is well aware of its implications. Not sure it's suitable for beginners... Sep 24, 2022 at 12:49
  • If all your functions do is to return a string literal, then just use the string literal directly, those functions are not necessary. The pattern you sited do that because the string literals are stored in the flash memory (PROGMEM) and therefore need to be copy-out into RAM, this however depends on your chip architecture, you need copy-out from FLASH to RAM for classic AVR chips (like ATmega328), however it doesn't required for ARM chips, ESP or even modern AVR chips released since 2016...
    – hcheung
    Sep 24, 2022 at 14:46
  • @hcheung: I believe those string literals are here only to provide simple dummy examples, and the actual library is going to build the strings from data gathered at run time. Sep 24, 2022 at 16:16

2 Answers 2

1

[The OP asks:] For ESP32/ESP8266 they are okay as they have big memory but for Arduino Uno boards I see a lot of discussions about not using String because it is bad and causes memory fragmentation etc.

On Unos and other boards with very low memory, fragmentation won't take very long to happen. On boards with larger memories, it will merely take longer! but it will happen; it's just a difference of when. (And for completeness, I should say that allocating a few objects, once, and within the device's available free memory, won't cause fragmentation, any more than static allocation would).

So why is allocation/deallocation used at all [I ask, rhetorically...]?

On systems running a full OS, driven by ourselves with a keyboard and mouse, most application programs are transient - they start up, do a job, and exit; even longer running ones like office applications are fairly speed-limited by us slow humans. And if a program crashes, we're still sitting there to re-run it, and no external device gets left hanging for hours or days, maybe destroying itself (by overheating, perhaps), maybe injuring or killing a patient (in the case of a medical device), injuring or killing many people (in case of a vehicle's engine- or flight controls).

Otherwise, run-time memory allocation is a convenience for the programmer.

So what to do in an embedded system, instead [asks I]?

I use two techniques to get around the crash-proneness of the compiler-libraries' allocator/deallocators:

  1. When I write a library, I design it to expect an (object *) from the caller, as you anticipated in your 'doubt #3'. All the library has to do is put its result there; the memory will already have been allocated. The library doesn't need to know or care how it was allocated. Does this sound like I'm avoiding solving the problem and leaving memory issues for some else to deal with? Yes, indeed, and it is deliberate! The user of my library - you, (or me, some other day), the application writer/system architect - is the best one to anticipate their particular system's memory needs, and to decide how best to meet them.

    Writing my library this way lets the library-user decide whether a static buffer, a local one, or a dynamic allocation is the best fit for their application.

  2. I write my own memory management (probably at the application level rather than in the library). This almost always takes the form of an array of fixed-length buffers. No matter how much memory the caller asks for, as long as it is less than that fixed size, and one is available, then the call succeeds and a buffer will be returned; otherwise, the call fails. The caller doesn't know, or need to know, that the buffer returned to them might be larger than they requested.

    Why is this technique any better? malloc()/free() and new()/delete() break up free memory into ever smaller pieces, as they are requested. A fixed-size-buffer allocater does not break up memory.

A system using these techniques together avoids memory fragmentation. The only way such a system can fail (due to memory allocation) is to not have enough in the first place, and the only fixes are 1) for the programmer to assign more memory for fixed-size buffers; or, if the board doesn't have enough more, then 2) build the system with a host board that has enough memory.

0

I often see this pattern done when returning char pointers like in this Time.cpp wherein a static buffer is used and defined at the implementation file (test.cpp) and all functions(getCharPointer, getAnotherCharPointer) that needs to return a char pointer just manipulates the static char buffer by using strcpy. Is this the proper way to go in Arduino programming?

Arduino programming is C++ programming. You can return a pointer to a static buffer, of course. But the user of your function needs to know that if they call the function a second time the data will be overwritten. So, they would need to strcpy (that is, copy) the data before it is overwritten.

Personally I would stay away from the String class, unless it is being used only occasionally. It is fine with strings that are always the same size, but if they are not, memory fragmentation will be an issue.

Your Answer

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

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