Curious about the best way of returning arrays from a function [solved]

Hi,

I read somewhere that some standard C functions do not work as well in the Arduino interface so I decided to see if it was still possible to return an array from a function.

From C language, I know that it is possible to return an array by declaring a variable as static or using malloc. The code that I wrote is shown below:

#define SIZE 10

void setup() {
  Serial.begin(9600);
}
// Returning a reversed array with malloc
int *func1(int *x) {
  int *p = malloc(sizeof(int)*SIZE);
  for (int i = 0; i < SIZE; i++) {
    p[i] = x[SIZE - 1 - i]; // Reverse array x and store result in p
  }
  return p;
}
// Returning a reversed array declared as static
int *func2(int x[]) {
  static int p[SIZE] = {0};
  for (int i = 0; i < SIZE; i++) {
    p[i] = x[SIZE - 1 - i]; // Reverse array x and store result in p
  }
  return p;
}
void print_array(int *y) {
  for (int i = 0; i < SIZE; i++) {
    Serial.print(y[i]);
    Serial.print(" ");
  }
  Serial.print("\n");
}
int x[SIZE] = {1,2,3,4,5,6,7,8,9,10};
int *y;
int counter = 0;
int value = 0;
void loop() {
  y = func1(x);
  Serial.print("Output of function 1: ");
  print_array(y);
  free(y);
  delay(1000);
  Serial.println();
  Serial.print("Output of function 2: ");
  y = func2(x);
  print_array(y);
  delay(1000);
}

Both of the functions work as expected. However, I am aware that an Arduino program operates a bit differently from a C program since it runs on an embedded system. From the two array functions that I wrote, which method is better when programming an Arduino board?

1 Like

Where did you get it from?
Arduino programs are written in C/C++ and work exactly the same as on any other system with these languages.

Arduino programs are written in C/C++ and work exactly the same as on any other system with these languages.

I read somewhere that you cannot use C functions such as scanf, printf, fprintf, fopen and fclose as they do not work properly on the Arduino interface.

the problem you can get on microcontrollers with small SRAM such as the UNO and Mega is that even if you free up the allocated memory you can fragment the memory and end up with the program crashing
this is why the use of C++ classes such as String , std::vector, etc is not recommended with UNO and similar micros

C is considered a systems language because it can directly access hardware and well suited for embedded applications. Code for many microprocessor based systems is written in C

before C, operating systems were written in assembly language. But because C could directly access memory mapped I/O, various drivers could be written in C making it more portable between different processor architectures. (yes some assembled is needed to access registers).

where is the malloc'd memory freed?

there's no difference between the above 2. However, it's more conventional that an additional argument would specify the size of the array, not to be exceeded and the function returns the amount of data copied into the array. see Serial readBytesUntil()


you might be interested to know that Arduino does support sprintf() although the %f conversions are not support. however, there is dtostrf()

Which Arduino board are you using ?

Instead of malloc, consider using new. Or better yet, do not allocate memory in a function and return a raw pointer at all.

This will lead to some counterintuitive behaviour. E.g.,

int* a {func2(x)};
int* b {func2(x)};
  
b[0] = 100;
Serial.println(a[0]);  // Prints "100".

Instead of returning an array, you could consider passing a reference to an array. For example:

template <size_t n> void myFunction(int (&a)[n], int const (&b)[n]) {
  for (size_t i {0}; i < n; i++) {
    a[i] = b[n - i - 1];
  }
}

Usage:

int b[] {1, 2, 3};
int a[3] {};
myFunction(a, b);
// `a` now contains 3, 2 and 1.

Alternatively, you can use something like the following (maybe in combination with the previous suggestion as an interface):

void myFunction2(int a[], int const b[], size_t const n) {
  for (size_t i {0}; i < n; i++) {
    a[i] = b[n - i - 1];
  }
}

If the size of the array can not be determined at compile time, I would recommend using std::vector or alternatives if the standard template library is not available for your platform.

I have freed the memory in the loop. I have included comments below for easier reading

void loop() {
  y = func1(x);
  Serial.print("Output of function 1: "); // Print output from function 1 (using malloc)
  print_array(y);
  free(y);  // Free the allocated memory after using the function
  delay(1000);
  Serial.println();
  Serial.print("Output of function 2: "); // Print output from function 2 (using static variable)
  y = func2(x);
  print_array(y);
  delay(1000);
  Serial.println();
}

I have an Arduino Uno and an Arduino Mega 2560. The code that I wrote to return an array works for both boards.

Is it not necessary to include equal signs for variables a, b and the index?

Heh heh. That piece of advice, complaining about un-owned pointers, immediately follows the advice "don't use arrays." :slight_smile:
Is this general concept of ownership described somewhere, in a C-programmer-friendly way? I've never heard of unique_ptr<> (I guess it's part of STL, which the AVR doesn't support by default.)
Also: "we need/use owning "raw pointers" as well as simple pointers in the implementation of our fundamental resource handles."

Using a malloc()ed or new() array (pretty much the same thing on Arduino) means you have to remember to free the memory, and can possibly end up fragmenting the heap anyway.

Using a static local array means that subsequent calls to the function will destroy the results returned by previous calls.

You get to choose!

Neither. The caller should provide the buffer and, when appropriate, the size of the buffer.

But that's true for C programming in general.

Easy to understand if you bear in mind: a function (on all MCU IDEs) can only return an INT/UINT value (16 or 32bit value, depends on MCU platform).

A return is just a single value, not a structure or "many return values".
BUT:
you can return a pointer (which is just a single value, in same size as an INT):
If the return value is a pointer and it points to a memory location where all the changed (and return) parameters are stored - you can do:

char * returnAString(void) {
    static myString;
    return myString;
}

Just think about "call by value vs. call by reference".
You can also "return" a complex structure via "call by reference": you provide a pointer to YOUR data structure which should be modified by your function.
Example:

void PleaseFillMyResult(char *result)
{
     strrcpy(result, "YourResult");
}

Distinguish between values as result (which can be only an integer value or pointer (and address) or modifying a memory location as "result" where you share the address (pointer) where to modify. The result is: the memory location was modified ("call by reference").

Yes, "call by reference" means: the caller has to provide the memory (buffer).
On "return by reference", e.g. to return a pointer - the called function can allocate the buffer or point to the memory used by the caller.

As real single "return by a variable" : only a single value or a pointer can be returned, not a complex type (such as strings or structures). They are returned "by reference".

"call by value":
a copy of the value is provided to the called function: any change does not have an effect on callers value

"call by reference":
a pointer to a shared memory location is provided: the function changes the memory so that the caller sees the change/difference afterwards.

"return by reference":
a pointer is returned, allocated and filled by the called function. The caller will use the "shared memory" used by the called function.

"return by value":
Only a single, simple value (INT) can be returned.

Complex returns work only as "call by reference" or "return by reference". Just single values, such as INT values work as "return by value".

Incorrect.

Again, incorrect.

I presume you are referring to the {}-initializer syntax?

I would not go that far, maybe: "prefer a vector over a dynamic array".

Maybe something like this? I am not sure about the C-programmer friendliness though.

Sure, but you want to manage them in such a way that every new is paired with a delete.

If we follow this idea through for a dynamic array, we get a vector.

In this case maybe, but new additionally calls the constructor, so for non-POD types the behaviour is significantly different.

I "love" people telling me "incorrect" w/o to provide hints what is wrong.
Please, how do you do a return from a C/C++ function, e.g. with two values?

The return of a function is a single register!

you pass (non null) pointers or references as arguments allowing values to be returned to the caller. the function itself returns a value indicating that it was successful.

But it's item R.2 in the reference you linked, just before the R.3 advice against raw pointers.

R.2: In interfaces, use raw pointers to denote individual objects (only)

Arrays are best represented by a container type (e.g., vector (owning)) or a span (non-owning).

(oops. No vector or span in avr-g++ !)

That I have "mentioned": a pointer as reference - "return by reference".
But it is like a "shared memory".
Sure, using shared memory and referenced via a pointer ("by reference") - you can "return" any amount of data: but you do not return all the values - you just return the pointer to the memory where all was modified.

how many values do you need to return?

you don't return the pointer, the pointer passed to the function is the address of the memory accessible in the calling function that you write in to. i can be an array that can be populated with many values