Sunday, December 19, 2010

Returning arrays in C++ (including multi-dimensional arrays)

Last article we were talking about passing arrays as arguments and it got a bit long, so this time I'm going to try to keep it shorter.

First I'll say that like when we passed an array by value in the last article, there is no built in easy way to return arrays by value, so we have to use workarounds.

The first thing I'll show is some WRONG CODE that beginners might try to use:

int* getArray() {
int my_arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
return my_arr;
}

int main() {
int* arr = getArray();
arr[1] = 4;
for(int i = 0; i < 10; i++) {
cout << arr[i] << endl;
}
return 0;
}


Can you see the error?
Well the problem is that in the function getArray() the array 'my_arr' is being allocated on the stack, and then you are returning a pointer to it. By the time you're back in the function main() 'my_arr' is not guaranteed to be valid anymore so using it as if its valid will cause undefined behavior. So don't do this.

Now lets look at some correct ways to do this.
One way is to use dynamic memory allocation to allocate the array on the heap and then pass a pointer to it, and then consider that memory as an array.

It looks like this (also note that I'm going to use a multi-dimensional array so people know how to do this):


const int n = 5;
const int m = 5;

int* getArray() {
int* arr = new int[n*m];
for(int i = 0; i < n*m; i++) arr[i] = i;
return arr;
}

int main() {
int* ptr = getArray();
int (&arr)[n][m] = (int(&)[n][m])*ptr;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
cout << arr[i][j] << endl;
}}
delete[] ptr;
return 0;
}


In the function getArray() we allocate a chunk of memory the size of n*m*sizeof(int) bytes. Then we initialize it to the numbers 0...n*m-1, then we return it as an int*.
Back in the function main() we get the ptr and then consider it as a multi-dimensional array by using references. Here you can see how to cast a pointer to an array (for single-dimensional arrays just ignore the extra [m] part).
Lastly we need to remember to delete[] the ptr, because we allocated this memory on the heap so it won't automatically delete it for us.


Since the above method has us needing to delete[] our memory ourselves, it is slightly inconvenient. This last approach doesn't have that problem, we use a struct like we did when we passed arrays as arguments in the last article.

const int n = 5;
const int m = 5;

struct arrayStruct {
int arr[n][m];
};

arrayStruct getArray() {
arrayStruct t;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
t.arr[i][j] = i*m + j;
}}
return t;
}

int main() {
arrayStruct a = getArray();
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
cout << a.arr[i][j] << endl;
}}
return 0;
}


When we use a struct to encapsulate the array, c++ creates a default copy constructor that will copy our data created in getArray() to our 'a' struct in main().

If we didn't want to access the array by using 'a.arr[i][j]', we could again use references like so:

int main() {
arrayStruct a = getArray();
int (&arr)[n][m] = a.arr;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
cout << arr[i][j] << endl;
}}
return 0;
}


We have now seen a couple ways to do this, there are more ways to accomplish this task but this blog post would get huge if I continue listing the various ways.

3 comments:

  1. hi,
    I am using struct operation in my code to save output of my program which is a 2D array. So far I can do this ; if my array dimension is lower for example- if I have initialized 8 2D arrays with dimensions as [10][10] of type double . They are being used to save matrix result generated my program code in the main function. But now I want to change the dimensions to a larger value say [ 1000][1000] but is unable to do so as I am causing the stack to overflow can you please tell me how to overcome this problem.

    ReplyDelete
    Replies
    1. What you should do is not allocate that struct on the stack. So either declare the struct object outside of the function, or use dynamic allocation to allocate it on the heap. (i.e. use the 'new' operator, MyStruct* p = new MyStruct()).
      You can also use the "static" keyword, "static MyStruct st;" although keep in mind this won't be thread safe if you do.

      Delete
  2. In the line:
    int (&arr)[n][m] = (int(&)[n][m])*ptr;
    Where the results retrieved from the function are stored into a new array "arr", how can i replace the array "arr" (on the LHS), with a function that already exists in the scope, meaning that i would assign the array returned from the function to an existing function.

    ReplyDelete