Returning a two-dimensional array in 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: https://i.sstatic.net/fGbNg.png

Upvotes: 7

Views: 2411

Answers (7)

John Bode
John Bode

Reputation: 123458

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)

Upvotes: 7

Christopher Key
Christopher Key

Reputation: 21

You can pass around arbitrarily dimensions arrays like any another variable if you wrap them up in a struct:

#include <stdio.h>

#define n 3

struct S {
  int a[n][n];
};


static struct S make_s(void)
{
  struct S s;

  int i, j;
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++)
      s.a[i][j] = i + j;
  }

  return s;
}

static void print_s(struct S s)
{
  int i, j;
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++)
      printf(" %d", s.a[i][j]);
    printf("\n");
  }
}

int main(void) {
  struct S s;

  s = make_s();
  print_s(s);

  return 0;
}

Upvotes: 2

legends2k
legends2k

Reputation: 32884

In C there's only pass/return by value (no pass by reference). Thus the only way of passing the array (by value) is to pass its address to the function, so that it can manipulate it through a pointer.

However, returning by value an array's address isn't possible, since by the time control reaches the caller, the function goes out of scope and its automatic variables go down with it too. Hence if you really have to, you can dynamically allocate the array, populate and return it, but the preferred method is passing the array and leaving the onus of maintaining the array to the caller.

As for the error, the only warning I get in GCC for this is warning: 'return' with a value, in function returning void which is simply meaning that you shouldn't return anything from a void function.

void new_array (int x[n][n]); what you're really doing here is taking a pointer to an array of n integers; the decayed type is int (*x)[n]. This happens because arrays decay into pointers generally. If you know n at compile time, perhaps the best way to pass is:

#define n 3
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;
    }
  }
}

And call it as

int arr[n][n];
new_array(&arr);

Upvotes: 2

Eric Postpischil
Eric Postpischil

Reputation: 222322

To return (a pointer to) a newly-created array of dimensions known at compile time, you can do this:

#define n 10 // Or other size.

int (*new_array(void))[n]
{
    int (*x)[n] = malloc(n * sizeof *x);
    if (!result)
        HandleErrorHere;

    for (int i = 0; i < n; ++i)
        for (int o = 0; i < n; ++o)
            x[i][o] = InitialValues;

    return x;
}

…
// In the calling function:
int (*x)[n] = new_array();

…
// When done with the array:
free(x);

If the size is not known at compile time, you cannot even return a pointer to an array. C does support variable-length arrays but not in the return types of functions. You could instead return a pointer to a variable-length array through a parameter. That requires using a parameter that is a pointer to a pointer to an array of variable length, so it gets somewhat messy.

Also, the preferred choices between allocating an array in the caller dynamically, allocating an array in the caller automatically, allocating an array in the called function dynamically and using variable-lengths arrays or fixed-length arrays or even one-dimensional arrays with manual indexing depend on context, including what how large the array might be, how long it will live, and what operations you intend to use it for. So you would need to provide additional guidance before a specific recommendation could be made.

Upvotes: 3

Samuel Edwin Ward
Samuel Edwin Ward

Reputation: 6675

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.

Upvotes: 4

barak manos
barak manos

Reputation: 30136

You are probably declaring n as a constant integer:

const int n = 3;

Instead, you should define n as a preprocessor definition:

#define n 3

Upvotes: 1

Carl Norum
Carl Norum

Reputation: 224854

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;.

Upvotes: 4

Related Questions