7

I have a function that needs to return 3 int values. I currently use an array declared in the global scope and I ask the function to write into them. Is there a way to make a function return an array?

Here is what I want to do:

int function()
{

int array[3];

array[0] = 19;  //these are just random numbers

array[1] = 7;

array[2] = 69;

return array[]; //is this possible??

}
  • 1
    It won't work, or at least not reliably: you could only return a pointer to memory that is no longer allocated (bad). You can return an array embedded into a struct. Since it's only 3 ints, it should be fine. – Edgar Bonet Oct 14 '16 at 18:40
  • That actually anwsers my question. You should have put it as an anwser, I'll acept it. – Dat Ha Oct 14 '16 at 18:50
4

I have a function that needs to return 3 int values.

In the interests of completeness I'll suggest another method. Let's assume that you really need 3 values and not 300.

void foo (int & a, int & b, int & c)
{
  a = 19;  //these are just random numbers
  b = 7;
  c = 69;
}

void setup() {
  Serial.begin (115200);
  int x, y, z;
  foo (x, y, z);
  Serial.println (x);
  Serial.println (y);
  Serial.println (z);
}

void loop() {
}

This conceivably will use less memory because the compiler may be able to optimize all 3 values into registers. In fact in that specific example, it has optimized away the entire function call:

000000be <setup>:
  public:
    inline HardwareSerial(
      volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
      volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
      volatile uint8_t *ucsrc, volatile uint8_t *udr);
    void begin(unsigned long baud) { begin(baud, SERIAL_8N1); }
  be:   26 e0           ldi r18, 0x06   ; 6
  c0:   40 e0           ldi r20, 0x00   ; 0
  c2:   52 ec           ldi r21, 0xC2   ; 194
  c4:   61 e0           ldi r22, 0x01   ; 1
  c6:   70 e0           ldi r23, 0x00   ; 0
  c8:   82 e2           ldi r24, 0x22   ; 34
  ca:   91 e0           ldi r25, 0x01   ; 1
  cc:   0e 94 64 01     call    0x2c8   ; 0x2c8 <_ZN14HardwareSerial5beginEmh>
  d0:   4a e0           ldi r20, 0x0A   ; 10
  d2:   50 e0           ldi r21, 0x00   ; 0
  d4:   63 e1           ldi r22, 0x13   ; 19  <---------- x
  d6:   70 e0           ldi r23, 0x00   ; 0
  d8:   82 e2           ldi r24, 0x22   ; 34
  da:   91 e0           ldi r25, 0x01   ; 1
  dc:   0e 94 4d 03     call    0x69a   ; 0x69a <_ZN5Print7printlnEii>
  e0:   4a e0           ldi r20, 0x0A   ; 10
  e2:   50 e0           ldi r21, 0x00   ; 0
  e4:   67 e0           ldi r22, 0x07   ; 7   <---------- y
  e6:   70 e0           ldi r23, 0x00   ; 0
  e8:   82 e2           ldi r24, 0x22   ; 34
  ea:   91 e0           ldi r25, 0x01   ; 1
  ec:   0e 94 4d 03     call    0x69a   ; 0x69a <_ZN5Print7printlnEii>
  f0:   4a e0           ldi r20, 0x0A   ; 10
  f2:   50 e0           ldi r21, 0x00   ; 0
  f4:   65 e4           ldi r22, 0x45   ; 69  <---------- z
  f6:   70 e0           ldi r23, 0x00   ; 0
  f8:   82 e2           ldi r24, 0x22   ; 34
  fa:   91 e0           ldi r25, 0x01   ; 1
  fc:   0c 94 4d 03     jmp 0x69a   ; 0x69a <_ZN5Print7printlnEii>

00000100 <loop>:
 100:   08 95           ret
7

There are several ways to achieve what you want:

The way that does not work

Did you try to compile the code you gave in the question? Then you probably noticed it does not compile. I tried and got: “error: expected primary-expression before ‘]’ token”, meaning the compiler expected return array[some_index];.

The way that almost works

Remove the brackets and just return array:

int *function()
{
    int array[3];
    array[0] = 19;
    array[1] = 7;
    array[2] = 69;
    return array;
}

This may work, or not, depending on the surrounding code, on how the optimizer processes your program, and on how lucky you are when you test it. It is called “undefined behaviour” and is something you should always avoid.

Here is what happens: in C (and C++) you cannot return an array. The identifier array “decays” to a pointer pointing at its first element. Then return array is essentially equivalent to return &array[0]. The problem is that, since the array is allocated in the function's stack frame, it ceases to exist when the function returns, thus the caller gets a pointer to an area of memory that is not allocated anymore. Likely memory corruption ahead. The compiler warned me when I tried to do that: “warning: address of local variable ‘array’ returned”. It's a very serious warning that should never be ignored.

The simplest fix: make the array static

As suggested by Chris Stratton in a comment, you can make the array static, so it will be allocated for the whole life of the program:

int *function()
{
    static int array[3];
    array[0] = 19;
    array[1] = 7;
    array[2] = 69;
    return array;
}

The only catch is that the function is now not reentrant, meaning that every time you call it it will clobber the same array it returned on the previous calls. Depending on your use case, this may not be an issue, but it's something you need to be aware of.

Have the caller manage the allocation

A safer (reentrant) way is to have the caller provide the memory needed for the array. This is a very common method in C, and is suggested both by Harper Shelby's answer and Chris Stratton's comment:

int *function(int array[3])
{
    array[0] = 19;
    array[1] = 7;
    array[2] = 69;
    return array;
}

The caller can allocate in static memory, or in it's own stack frame, or on the heap... but you do not care, the allocation is the caller's problem now.

A couple of things to note here:

  • The prototype is equivalent to int *function(int *array): the function receives only a pointer. Writing array[3] instead of *array has the sole effect of documenting that the function expects the pointer to point to somewhere with room for 3 values. You could document that in a comment instead.
  • The function could return void, since the caller presumably knows the address it is giving you. Returning that same address is just for convenience, as it can help you chain calls like another_function(function(array)).

Return the array in a struct

One may wonder: why can't we return an array in the first place. I do not know for sure why the authors of the language made this choice, but one possible explanation is that it is expensive to return a big array by value, as it involves copying the whole of it. Now, if your array is really only three ints, then that argument does not apply, and you may reasonably want to really return the whole array by value. This can be done by embedding it inside a struct:

// Struct definition.
struct Array3 {
    int array[3];
};

Array3 function()
{
    Array3 a;
    a.array[0] = 19;
    a.array[1] = 7;
    a.array[2] = 69;
    return a;
}
  • Which of those would use the less memory possible? – Dat Ha Oct 14 '16 at 21:04
  • Making the array global and letting both functions (caller and called) assume the use of that array, only requres the space for the 3 ints (6 bytes) but they remain reserved for the purpose for the entire program run, whether they are in use or not. The other option is for the caller to allocate the array memory in its own stack and pass the called function a pointer to it. That uses not only the three ints but possibly, memory for the pointer as well (unless the compiler chooses to pass the pointer in registers - but lets assume worst case - that it gets passed in stack memory). ... – JRobert Oct 15 '16 at 0:21
  • ... Then there is the additional memory (two more bytes) for the pointer), but the pointer's memory is only reserved while the called function is running. The array itself is only reserved while the caller (and thus the called) function is active. When the caller returns to its caller, the array space becomes free. So which uses less memory? It depends on when you look! – JRobert Oct 15 '16 at 0:21
0

There are 2 ways to do what you want in C. One relies on the function caller managing memory, the other on the "library" code managing the memory. If you go with the caller-managed memory, your function accepts a buffer and fills it. For "library" managed memory, you provide additional functions to allocate and release the buffer that the caller then calls

If you want to use C++ idioms, you can make a class (possibly static) that allocates the memory and provides access to the data.

  • If you are going to allocate memory in the function/method be sure to do it in a lasting way - something placed on the stack for example will not outlive the return of the function call - it might still be there, but only unreliably. Personally I'd use either a static allocation, or the caller's stack, as lasting runtime allocation introduces failure modes and is going to require about as much space to track the allocation as the 6 bytes that 3 Arduino ints would actually consume. – Chris Stratton Oct 14 '16 at 18:59
  • Absolutely true - I didn't go in to all the details, just pointed out the basic strategies. In the Arduino environment, having tracked allocations (and in most cases, any dynamic allocations) is probably not the way to go, for the reasons you mention. – Harper Shelby Oct 14 '16 at 19:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.