Functions returning variables

I’d be really grateful if somebody could give me some guidance on this.
I’m still new to Arduinos and I am trying to tidy up some sketches by using functions, or more specifically functions that return values. This is new to me.
In the sketch attached I pass a float to a function called NumToChar() hoping to get a character string returned.
In the function I Serial.print the character string before returning it just to make sure that the function is doing what it is supposed to, but when the variable is returned it is empty.

image

I’ve included another simple function for area just to make sure that I am returning variables.
Can anybody tell me why the Serial.println(NumToChar(3.142)); prints nothing?

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

void loop() {

  Serial.println(NumToChar(3.142));

  Serial.print("Area =");
  Serial.println(areaCalc(4, 5));
  delay(3000);
}

/////////////////////////////////////////////////////////////
char* NumToChar(float number) 
{
  String numberAsString = String(number, 3) + ",";    // convert float to String
  int str_len = numberAsString.length() + 1;          // find string length
  char numberAsChar[str_len];                         //declare char of correct length
  numberAsString.toCharArray(numberAsChar, str_len);  // convert String to char string

  Serial.print(numberAsChar);

  return numberAsChar;
}

/////////////////////////////////////////////////////////////
int areaCalc(int length, int width) {
  int area = (length * width);
  return area;
}

as numberAsString is a String why not return a String?

String NumToChar(float number) 
{
  String numberAsString = String(number, 3) + ",";    // convert float to String

what microcontroller are you using?
if a low power micro such as a Uno, Mega, etc avoid using String class as they can fragment memory, cause problems and possible crash the program#

This variable numberAsChar is deleted as soon as the function returns

Either make this variable static, or use a global variable, or add another parameter to the function, which will be a char array where the result of the function can be stored

Here is an example F8nxtH - Online C++ Compiler & Debugging Tool - Ideone.com

For safety, you can add another parameter to the function, which will be the size of that char array, and make sure your function doesn't write more than that into the char array

Here's what Arduino wants you to know about variable scope

and that's just, as usual, a brief flight over part of the matter.

Cast a wider google net

   variable scope in C++

and poke around a bit. Read as if you know what they talkin' 'bout and pretty soon you will.

HTH

a7

I'm using an esp32.

the use of the String class is generally OK on the ESP32 and similar devices

This variable numberAsChar is deleted as soon as the function returns

I'm a bit confused by this. The other function for area has area variable defined in the function only but still returns the area value.

I may try defining the char string as a set string length and return it to another variable with the same string length. It's worth a try.

That was explained in reply #3.

You can always return the value and print that value (or do anything else with it). Why bother to return a C-string or String object?

The function that I am using to write data to an SD card uses char strings. I could probably change that function but that would lead me to further problems.

At the moment I am finding that every obstacle that I overcome requires me to overcome other obstacles, all of which require me to overcome other obstacles ad infinitum.

It's 30 years since I was a graduate engineer but I'm feeling more like a graduate every day. I don't feel any younger though.

Is there some particular reason to use that function? SD.print() works with SD cards, exactly as you would expect Serial.print() to work.

Perhaps if you study the documentation and examples that come with the SD library, it will save time and headaches with your actual project.

you can use method String.c_str() to access the internal buffer of a String object and get a pointer to a C-type string, e.g.

void setup() {
  Serial.begin(115200);
  String data = "1234567890";
  const char* charPtr=data.c_str();
  Serial.println(data);
  Serial.println(charPtr);
}

void loop() {}

serial monitor displays

1234567890
1234567890

don't attempt to modify the internal string using the char pointer

On ESP8266 & ESP32 you have the luxury of using Serial.printf() and file.printf() and it works even for floats!

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

void loop() {

  Serial.printf("%.3f,\n", 3.142);

  Serial.printf("Area =%d\n", areaCalc(4, 5));
  delay(3000);
}

/////////////////////////////////////////////////////////////
int areaCalc(int length, int width) {
  int area = (length * width);
  return area;
}

Simples!

That's a very good observation and a good question by implication.

char* NumToChar(float number) 
{

The function definition promises to return a pointer to char, that's what the * means in this context.

Inside the function, you create an array of characters. In C/C++, arrays can be handed around just by using a pointer. The name of the array can be thought of as is in fact a pointer to char.

  char numberAsChar[str_len];                         //declare char of correct length

So the function happily returns said pointer, viz:

  return numberAsChar;

but unfortunately, the array that the pointer is pointing to ceases to exist at around exactly the same time, when the function exits.

The solution offered above

means that the variable remains in existence, and retains its contents, across calls to the function.

One small detail is that since a static array must have its size determined ahead of time, you cannot make the array size differ from call to call, so it would have to be a fixed size, and one would need to ensure that it was large enough for all things that were put into it.

Just go with other solutions and trust that someday this will make sense. :expressionless:

a7

I liken the situation to this. Imagine you are hiring a private investigator because you need a picture of a mystery guy so you can identify him.

The int function is like a PI that promises to get you a picture of the guy. And he does bring you back a picture of the guy.

The char* function returns a pointer. That's like a PI that says he will get you the address of the house where the guy lives so you can go get your own picture. And he does. And it is the correct address. The problem is that the guy has moved out before you got there to take his picture.

HTH

1 Like

Static local variable are also a problem if the function is intended to be recursive or reentrant.

2 Likes

Once upon a time the response would have been, "when does that ever matter in Arduino."

I guess it's a nice problem to have now.

It would be unusual but not inconceivable, like strtok() which now has its better modern version
strtok_r(), the code for which makes interesting reading.

a7

What I meant was that today it is conceivable. Once upon a time it was all just one thread on an AVR. But some of these new boards are kind of impressive. A lot of old arguments don't hold anymore.

No, I got that. I came up from playing with things that made the UNO feel like I'd never run out of anything… most of what I do is like cutting butter with a blowtorch using the now "low end" micros.

I feel bad for ppl coming the other way and wondering where all the things they're used to are to be found.

I wish I could quote one recent newcomer who said something like "normally I'd just thread this out".

a7

Even in a single-thread, single-core environment, functions that are called by both ISR and non-ISR code need to be reentrant-safe.

Recursive algorithms, admittedly, aren't seen much in Arduino Land. But they exist. For instance, in the "SD_Test.ino" example in the ESP32 core, the listDir() function is recursive.