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 :
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 [
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 ?
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);
}
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