Go Down

Topic: How to write a function that returns an array? (Read 3 times) previous topic - next topic

PeterH


You can't really return an array without using dynamic memory allocation (malloc/free).


It's been a couple of decades since I was into this stuff, but I don't think that's quite right.

Of course you're referring to arrays which are local variables, and the fact that the array name is syntactically equivalent to pointer-to-array-element. Returning a pointer to a local variable is not safe.

Others have already pointed out that if you have an array which is not allocated on the stack, you can return that just fine. Well, strictly you return the pointer to the array, but it remains valid after the return executes.

IIRC there is [at least] one situation where you can declare a local variable allocated on the stack, and return that safely. If you declare a struct containing an array, you can assign instances of that struct to each other and that structure assignment does a shallow copy of the memory occupied by the struct. If the struct contains an array, the array elements are copied, too. In other words, this is safe:

Code: [Select]

typedef struct
{
char text[32];
} Name;

Name getName()
{
Name result;
sprintf_s(result.text, sizeof(result.text), "Me");
return result;
}


I'm not suggesting this is a good design to solve the original problem, but it's a way to return an array.
I only provide help via the forum - please do not contact me for private consultancy.

Nick Gammon

Assuming you are correct about the shallow copy, you still need to have the array in the caller (for it to be copied into). So you don't achieve anything over passing that other array by reference anyway.

I qualified my answer with the word "really" because you can return arrays, sure:


  • If you don't mind the program crashing

  • If you return a static variable



But the static variable approach has flies on it. Say you return a pointer to this static array (which "lives" inside the function) then you can't afford to call the function twice and expect both variables to be valid. And that's easy enough to do:

Code: [Select]
char * bar (int i)
  {
  static char buf [10];
  itoa (i, buf, 10);
  return buf;
  }

void setup ()
{
  Serial.begin (115200);
  char foo [50];
  sprintf (foo, "%s %s", bar (1), bar (2));
  Serial.println (foo);
}

void loop () {}


This outputs:

Code: [Select]

2 2


Which might not be what you expect.

pYro_65

Quote
Assuming you are correct about the shallow copy, you still need to have the array in the caller (for it to be copied into). So you don't achieve anything over passing that other array by reference anyway.


I'm thinking along these lines too, however lifetime of temporaries can be extended passed that of the function, eliminating the need to shallow/deep copy.

Code: [Select]
typedef struct
{
char text[32];
} Name;

Name getName()
{
Name result;
sprintf_s(result.text, sizeof(result.text), "Me");
return result;
}

void loop()
{
  const Name &n_Name = getName();
  return;
}


By making a constant reference to the return value, its lifetime is extended to that of the reference.

Quote
The temporaries are r-value objects, which means that you can't change their values. So the usage of const reference is mandatory. In pre-standard it's was allowed to use non-const references for temporaries but in ISO-C++ it's a compilation error. Changing value of const object could lead to unexpected results.

Using const references to catch the return value of the function could be used as an optimization of avoiding creating temporary objects on function return.

liudr

Nick,

That example was tricky, took me some seconds to realize why the "2 2". Good one. I think this discussion ends up becoming a showdown of how deeply everyone understands the language, maybe not what OP wanted to know. Maybe someone should point a function doesn't return any variable, but only returns a value to be assigned to a previously declared variable instead. A variable includes memory location and value stored inside. Even returning pointers was returning the value of a pointer, not the pointer variable in the "return" command.

Nick Gammon

Quote
By making a constant reference to the return value, its lifetime is extended to that of the reference.

Yes but it's getting convoluted. This has the same effect and doesn't use any more memory:

Code: [Select]
typedef struct
{
   char text[32];
} Name;

void getName(Name & result)
{
   sprintf(result.text, "Me");
}

void loop()
{
 Name n_Name;
 getName(n_Name);
}

PeterH


Assuming you are correct about the shallow copy, you still need to have the array in the caller (for it to be copied into). So you don't achieve anything over passing that other array by reference anyway.



You're mistaken.

When the return type of the function is a struct, the space for the return value in the stack frame is the size of the struct (significantly, in this case, including the array elements). The struct contains the array elements, not a pointer to the array. So you can execute:
Code: [Select]

Name myName = getName();

The return statement does a shallow copy of the struct (including the content of the array) into the return stack frame, and then as the call completes the return value is copied from the stack frame to the myName variable. It is exactly the same as when a scalar type is returned, except that it is applied to the entire struct.

The semantics of struct assignment and array assignment are very different in this respect. It's an inconsistency that was introduced to the language spec at a very early stage and is now so entrenched that I don't see it ever being corrected.
I only provide help via the forum - please do not contact me for private consultancy.

Nick Gammon


The return statement does a shallow copy of the struct (including the content of the array) into the return stack frame, and then as the call completes the return value is copied from the stack frame to the myName variable.


All I am saying is that in the example code just above no copying at all takes place. There is one instance "n_Name" which resides in the caller. The called function gets it by reference, no copying in, no copying out. Just the one piece of memory.

PeterH



The return statement does a shallow copy of the struct (including the content of the array) into the return stack frame, and then as the call completes the return value is copied from the stack frame to the myName variable.


All I am saying is that in the example code just above no copying at all takes place. There is one instance "n_Name" which resides in the caller. The called function gets it by reference, no copying in, no copying out. Just the one piece of memory.


I accept and agree that it is possible to have the caller pass in the variable address/reference and have the called function put them in that location.

The point I was trying to make is that without using those approaches, and just using a plain old function return value, it is possible to return the array (in its entirety, including all the elements) as part of a struct value returned by the function.

Code: [Select]

Name myName = getName();
I only provide help via the forum - please do not contact me for private consultancy.

Nick Gammon

OK, I see. A shallow copy you say? So no copy constructor, or operator= called? I've seen things like this before:

Code: [Select]
String foo ()
  {
  String bar = "hello world";
  return bar;
  }


They seem to work. You could have to look closely to see if the exact object was returned (which you seem to be saying would happen) or a constructed copy.

Go Up