Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a two-dimensional array in C?

Tags:

c

I recently started programming C just for fun. I'm a very skilled programmer in C# .NET and Java within the desktop realm, but this is turning out to be a bit too much of a challenge for me.

I am trying to do something as "simple" as returning a two-dimensional array from a function. I've tried researching on the web for this, but it was hard for me to find something that worked.

Here's what I have so far. It doesn't quite return the array, it just populates one. But even that won't compile (I am sure the reasons must be obvious to you, if you're a skilled C programmer).

void new_array (int x[n][n]) {
  int i,o;

  for (i=0; i<n; i++) {
      for (o=0; o<n; o++) {
        x[i][o]=(rand() % n)-n/2;
      }
  }

  return x;
}

And usage:

int x[n][n];
new_array(x);

What am I doing wrong? It should be mentioned that n is a constant that has the value 3.

Edit: Here's a compiler error when trying to define the constant: http://i.imgur.com/sa4JkXs.png

like image 564
Mathias Lykkegaard Lorenzen Avatar asked Jan 27 '14 21:01

Mathias Lykkegaard Lorenzen


3 Answers

C does not treat arrays like most languages; you'll need to understand the following concepts if you want to work with arrays in C.

Except when it is the operand of the sizeof or unary & operator, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array. This result is not an lvalue; it cannot be the target of an assignment, nor can it be an operand to the ++ or -- operators.

This is why you can't define a function to return an array type; the array expression will be converted to a pointer type as part of the return statement, and besides, there's no way to assign the result to another array expression anyway.

Believe it or not, there's a solid technical reason for this; when he was initially developing C, Dennis Ritchie borrowed a lot of concepts from the B programming language. B was a "typeless" language; everything was stored as an unsigned word, or "cell". Memory was seen as a linear array of "cells". When you declared an array as

auto arr[N];

B would set aside N "cells" for the array contents, along with an additional cell bound to arr to store the offset to the first element (basically a pointer, but without any type semantics). Array accesses were defined as *(arr+i); you offset i cells from the address stored in a and dereferenced the result. This worked great for C, until Ritchie started adding struct types to the language. He wanted the contents of the struct to not only describe the data in abstract terms, but to physically represent the bits. The example he used was something like

struct {
  int node;
  char name[14];
};

He wanted to set aside 2 bytes for the node, immediately followed by 14 bytes for the name element. And he wanted an array of such structures to be laid out such that you had 2 bytes followed by 14 bytes followed by 2 bytes followed by 14 bytes, etc. He couldn't figure out a good way to deal with the array pointer, so he got rid of it entirely. Rather than setting aside storage for the pointer, C simply calculates it from the array expression itself. This is why you can't assign anything to an array expression; there's nothing to assign the value to.

So, how do you return a 2D array from a function?

You don't. You can return a pointer to a 2D array, such as:

T (*func1(int rows))[N]
{
  T (*ap)[N] = malloc( sizeof *ap * rows );
  return ap;
}

The downside to this approach is that N must be known at compile time.

If you're using a C99 compiler or a C2011 compiler that supports variable-length arrays, you could do something like the following:

void func2( size_t rows, size_t cols, int (**app)[cols] ) 
{
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;                   // the parens are necessary
  ...
 }

If you don't have variable-length arrays available, then at least the column dimension must be a compile-time constant:

#define COLS ...
...
void func3( size_t rows, int (**app)[COLS] )
{ 
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;
}

You can allocate memory piecemeal into something that acts like a 2D array, but the rows won't necessarily be contiguous:

int **func4( size_t rows, size_t cols )
{
  int **p = malloc( sizeof *p * rows );
  if ( p )
  {
    for ( size_t i = 0; i < rows; i++ )
    {
      p[i] = malloc( sizeof *p[i] * cols );
    }
  }
  return p;
}

p is not an array; it points to a series of pointers to int. For all practical purposes, you can use this as though it were a 2D array:

 int **arr = foo( rows, cols );
 ...
 arr[i][j] = ...;
 printf( "value = %d\n", arr[k][l] );

Note that C doesn't have any garbage collection; you're responsible for cleaning up your own messes. In the first three cases, it's simple:

int (*arr1)[N] = func(rows);
// use arr[i][j];
...
free( arr1 );

int (*arr2)[cols];
func2( rows, cols, &arr2 );
...
free( arr2 );

int (*arr3)[N];
func3( rows, &arr3 );
...
free( arr3 );

In the last case, since you did a two-step allocation, you need to do a two-step deallocation:

int **arr4 = func4( rows, cols );
...
for (i = 0; i < rows; i++ )
  free( arr4[i] )
free( arr4)
like image 80
John Bode Avatar answered Sep 30 '22 12:09

John Bode


Your function return void, so the return x; line is superfluous. Aside from that, your code looks fine. That is, assuming you have #define n 3 someplace and not something like const int n = 3;.

like image 34
Carl Norum Avatar answered Sep 30 '22 12:09

Carl Norum


You can't return an array in C, multidimensional or otherwise.

The main reason for this is that the language says you can't. Another reason would be that generally local arrays are allocated on the stack, and consequently deallocated when the function returns, so it wouldn't make sense to return them.

Passing a pointer to the array in and modifying it is generally the way to go.

like image 39
Samuel Edwin Ward Avatar answered Sep 30 '22 13:09

Samuel Edwin Ward