Hello.
All your doubs are not ESP32 related but C/C++. So all explained here is for C and/or C++ compiling or code details.
Well, first things first:
Yes printf works fine in Arduino and ESP32, in all boards, or so I guess; printf is a C function that doesn't matter where you actually print.
But, it's a C function, so it spects char * type when %s format is used. String type or std::string are not a char *.
You can get the underliying char * by calling .c_str() on them. This way:
#include <Arduino.h>
void setup() {
Serial.begin(115200);
String str = "Hello, world!");
Serial.printf( "%s from ESP32!!", str.c_str() );
}
void loop() {
// Your code here
}
While using Serial.print or Serial.println, with String or str::string type, you actually call the perfect overloaded function so it works as intended.
Hey, you're not alone: takes me 2 months to realize that
Wanna deep dig? continue reading, it will be harder, but interesting for many people (I hope) :
About returning static variables
Back in the 90', Microsoft Visual C++ 6.0, the first version at least, had an error while implementing return statement and returning local non basic variables in C++ doesn't work. I'll explain how return works in the end of the post.
But first, we need some basis. So, let me explain a bit.
- We can return a local int, don't we?
- Yes, of course.
int giveMeANumber()
{
int i = std::srand(1, 100);
return i;
}
void loop()
{
Serial.printf( "Hey, that man gave me the number %d", giveMeANumber() );
delay(1000);
}
- So, why not to return a string ?
- Who says that? Sure we can! here you have:
#include <string>
std::string giveMeANumberAsString()
{
std::string i = std::to_string( std::srand(1, 100) );
return i;
}
void loop()
{
Serial.printf( "Hey, that man gave me the number %s", giveMeANumberAsString().c_str() );
delay(1000);
}
So, as you can see, there is not any need to use static String variable to return it.
You need a static variable if you return its address, because non static variable address will become invalid after return.
- Great, but I like string to be a char *, can you?
- Nop!
- Hey man, why?
Short answer:
Because char * doesn't have
an apropiate copy method, because it's neither a class nor struct nor a basic type; it's an array.
Long answer:
In C and C++, all basic variable types and structs (or classes) make a raw copy of themselfes while you do a simple assignement:
typedef struct {
int a;
int b;
}pair_t;
int setup()
{
pair_t pair1;
pair1.a = 10;
pair1.b = 20;
pair_t pair2 = pair1;
Serial.printf("pair1.a is %d, pair2.a is %d.\nAre de same? %s!\n", pair1.a, pair2.a, pair1.a == pair2.a ? "Yes" : "No");
Serial.printf("pair1.b is %d, pair2.b is %d.\nAre de same? %s!\n", pair1.b, pair2.b, pair1.b == pair2.b ? "Yes" : "No");
return 0;
}
For that reason, you cannot return and array. There is not that "apropiate raw copy". If you do an assignement, you only copy the basic type: the char * (the pointer value)
A raw copy of any variable type is just a memcpy. And a memcopy should be something like this:
void memcpy( *target, *origin, size_t len)
{
while( --len )
target[len] = origin[len];
}
When we assign a char * to another char *, the compiler does that:
char *a = "a";
char *b = "b";
a = b; // compiler makes: memcopy(&b, &a, sizeof(a));
&a gets the address of the adresss of the string ( char ** )
Sizeof(a) is the size of a char pointer, not the length of the string.
Now, what about return statement?
Well, return statement does many things and some non-intuitive if you don't think a little.
return statements are in the function scope. Meaning, inside function brakets. That may make you think that at return, the first think that compiler does is to destroy local variables.
But it's not that simple.
If return destroys local variables, the value in them should be not copied because... well.. they have been destroyed. At least, in C, unavailable because stack unwinds and the values are no longer reachable. In C++ it's worse, because of the class destructor that is called before stack unwind. So, in C++ there are more possibilities that class variables gets freed and even zeroed.
Actually, the first thing that returns does is to call the assignement or copy method of the class, if in the caller code there is an assignement, of course. After that, deletes the class (calling destructor) and unwinds the stack, and jumps program pointer back...
Take a look at this:
https://onlinegdb.com/VOpyxgT-C
One last thing to think. I promise.
When I'd face this printf "issue", makes me blow my head because if the string were short, it worked:
#include <Arduino.h>
void setup() {
Serial.begin(115200);
String str = "A");
Serial.printf( "Give me an A: %s!!", str );
}
void loop() {
// Your code here
}
So, what the hell is going on?
I guess that it is a compiler optimization work. But not sure...
Remember that compiler is smarter than we. Than me at least. And when it find literals, could mute your code to do the same but optimized.
Compiler and other IA doen't understand fine the "intention".
I remember a friend of mine explaining me that he write a long for loop, just to slow down process.
Something like: for( int i = 0; i < 100000; ++i )
So far so good... but only in debug mode. When in relase, with optimizations activated, compiler just skipped this for loop.
Sorry for this long post!!!!