dynamic allocation for char*

Hi,

I'm trying to allocate a char* dynamically to avoid fixed size char arrays. I saw that Strings aren't recommended so I tried to use char* instead but it's not that easy.

  • I'm working with an ESP8266
  • My goal is to copy 3 char arrays into one.
  • Here is my code:
char * cr;

void setup() 
{
  char ca[] = "test";
  char cb[] = "test2";
  char cc[] = "test3";
  int lena, lenb, lenc, total;
  
  Serial.begin(115200);
  while (!Serial) { }
  delay(200);
  Serial.println("Starting");
  // put your setup code here, to run once:
  lena = strlen(ca);
  lenb = strlen(cb);
  lenc = strlen(cc);
  total = lena + lenb + lenc + 3;
  Serial.println(total);
  cr = new char(total);
  
  strcpy(cr, ca);
  strcat(cr, "/");
  strcat(cr, cb);
  strcat(cr, "/");
  strcat(cr, cc);
  Serial.println(cr);
  
}
void loop() {
  // put your main code here, to run repeatedly:
}

It works perfectly with a fixed size array but char* keeps creating an exception :

Starting
17
test/test2/test3

--------------- CUT HERE FOR EXCEPTION DECODER ---------------

Exception (3):
epc1=0x40100718 epc2=0x00000000 epc3=0x00000000 excvaddr=0x40007cd9 depc=0x00000000

>>>stack>>>

ctx: sys
sp: 3fffecc0 end: 3fffffb0 offset: 0190

My C/C++ is a bit rusty so I'm certainly doing wrong somewhere but I can't find the reason.

Thanks a lot for your help.

cr = new char(total); This is asking for a char object... Did it work?

cr = (char*)malloc(total);

-jim lee

It works with your solution. I saw the "new" method on a forum and was not really confident about it. I should have sticked to the good old malloc.

Thank you :slight_smile:

jimLee:
cr = new char(total); This is asking for a char object... Did it work?

cr = (char*)malloc(total);

Don't use malloc in C++ unless you're purely allocating memory without creating any objects in it.
When you need dynamic allocation, your first choice should always be to use a container that handles allocation for you, like String, std::string, std::vector etc. If that doesn't fit your needs, use a smart pointer like std::unique_ptr. If you don't even have access to those standard library features, look for a third-party library that does it for you. Your last choice should be writing your own RAII wrapper that abstracts away the memory management. Under the hood, you can then use new char[total].
To reiterate: 99.99% of C++ programs should never make calls to new directly. Use standard containers and smart pointers. In practice, you should never use malloc in C++. The only use for it is writing allocators or low-level code in custom containers.

That being said, “using char*” is not going to help you here if you're going to be using dynamic allocation. Strings can be problematic because of the heap fragmentation they cause by using dynamic allocation. Manually allocating dynamic memory is worse than using String to manage the memory because you now risk memory leaks on top of the inherent fragmentation you might get from dynamically allocating memory.

If you want to avoid the problems with String, either use statically allocated buffers, or use String and use String::reserve() beforehand so you don't need to reallocate later when growing the strings. The latter is tricky, because it's not always clear which String operations allocate or create temporary Strings.

On an ESP8266, you usually have plenty of RAM, and many of the Core functions already heavily use Strings, so you can probably use String in your user code without worrying too much. You might have to watch your allocation patterns, but don't start prematurely replacing all Strings unless you have verified that you actually have a fragmentation problem.
You can use [

ESP.getHeapFragmentation()

](Libraries — ESP8266 Arduino Core 3.1.2-21-ga348833 documentation) to check how bad the fragmentation is.

Pieter

I agree with @PieterP. Stick with Strings they are much easier to code correctly than using low level c-string methods

I'm trying to allocate a char* dynamically to avoid fixed size char arrays.

Why ?

At some point in time you will need to need to declare an array large enough to hold the maximum combined contents of the three other arrays, so why not just declare the target array large enough to hold the maximum possible length of the three of them ?

I agree with @UKHeliBob.

Suppose you do use dynamically sized arrays, what are you going to do with the unused memory?

...R

Isn't that a little like asking.. "What you are going to do with all that money you saved?"

-jim lee

jimLee:
Isn't that a little like asking.. "What you are going to do with all that money you saved?"

You can use money that you save. Unused SRAM is as useful as empty airplane seats over the atlantic.

...R

Couldn't you use a function and allocate the variable length char string off the stack avoiding the problems with the heap?
I haven't tested the code below - can't find my Uno!

void setup() {
  // put your setup code here, to run once:

  char ca[] = "test";
  char cb[] = "test2";
  char cc[] = "test3";
  int lena, lenb, lenc, total;
 
  Serial.begin(115200);
  while (!Serial) { }
  delay(200);
  Serial.println("Starting");
  // put your setup code here, to run once:
  lena = strlen(ca);
  lenb = strlen(cb);
  lenc = strlen(cc);
  total = lena + lenb + lenc + 3;
  Serial.println(total);

  addstring (total, ca, cb ,cc);
    
  }
void loop() {
  // put your main code here, to run repeatedly:
   Serial.println ("loop");
}

void  addstring (int cLen, char *ca, char *cb, char *cc){
  
   char cr [cLen];

   strcpy (cr, ca);
   strcat (cr, "/");
   strcat (cr, cb);
   strcat (cr, "/");
   strcat (cr, cc);
   Serial.println (cr);
   }

countrypaul:
off the stack avoiding the problems with the heap

Don't the stack and heap share the limited SRAM on an Uno ?

...R

PieterP:
Don't use malloc in C++ unless you're purely allocating memory without creating any objects in it.
When you need dynamic allocation, your first choice should always be to use a container that handles allocation for you, like String, std::string, std::vector etc. If that doesn't fit your needs, use a smart pointer like std::unique_ptr. If you don't even have access to those standard library features, look for a third-party library that does it for you. Your last choice should be writing your own RAII wrapper that abstracts away the memory management. Under the hood, you can then use new char[total].

@PeiterP, I was with you up to that point. In fact, mostly due to exchanges I've had with you in this forum, I have been trying to incorporate more features of "Modern C++" into my code … at least on platforms that support the STL. But, lacking a real, proven STL implementation, I certainly trust my own coding skill to create pointer wrappers (or even handle raw pointers) much more than I trust some random "third-party library" that I might download from the Interweb. That's not quite as bad as following coding advice you find on Instructables, but it's close.

Robin2:
Don't the stack and heap share the limited SRAM on an Uno ?

...R

Yes, probably working from each end of the Sram - but I'm not certain. When you leave a function the stack is automatically freed up and you don't need to be concerned with fragmenting memory. When you allocate something off the heap you have to free that memory in code (when? - in some cases when leaving the function in other cases not etc.) and you also risk fragmenting the heap if another allocation off the heap is made after this one but not freed before this one (the arduino does not have any garbage collection for the heap afaik.
It really comes down to the purpose the op really has in mind, as the example given could easily use Serial.print() for all the items he puts in the new char array.

gfvalvo:
@PeiterP, I was with you up to that point. In fact, mostly due to exchanges I've had with you in this forum, I have been trying to incorporate more features of "Modern C++" into my code … at least on platforms that support the STL. But, lacking a real, proven STL implementation, I certainly trust my own coding skill to create pointer wrappers (or even handle raw pointers) much more than I trust some random "third-party library" that I might download from the Interweb. That's not quite as bad as following coding advise you find on Instructables, but it's close.

You're right, that statement might indeed need a bit of nuance, since there's a ton of bad libraries out there, especially for Arduino, so you definitely shouldn't trust “some random third-party library”, but find one that has unit tests, an active maintainer, documentation that shows the maintainer actually invested time in the library, etc.

That being said, writing your own containers is not trivial for beginning C++ programmers. Getting the constructor and destructor right is usually not an issue, but making sure the copy and move constructors and assignment operators do the right thing requires a bit more thought. Given the question OP has about allocations, using a library that is known to work is probably safer at this point.
@baddwarf if you would like to learn more about writing your own container, definitely read about the Rule of Three/Five/Zero.

Memory allocation and usage aside, it makes sense for variables to be declared in as limited a scope as possible. Apart from anything else this reduces the possibility problems caused by the same variable name being used with two different scopes, the classic example of which would be one being declared at global level and another with the same name being declared at local level and not being sure which is actually being used at local level

Having said that, careful use of global variables with sensible names is acceptable in most sketches on the Arduino and it reduces the need to pass variables to functions, but as with many things it is a matter of judgement

countrypaul:
Yes, probably working from each end of the Sram - but I'm not certain.

The heap grows "up" and the stack grows "down". When they meet ... trouble.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.