tags:

views:

594

answers:

4

How can I make a function which returns an array? I tried this

const int WIDTH=11;
const int HEIGHT=11;

int main() {
  char A[WIDTH][HEIGHT];
  A=rand_grid(WIDTH,HEIGHT);
  return 0;
}

// Initializes a random board.
char[][] rand_grid(int i, int k) {
  char* A[i][k];
  for(j=0;j<i;++j) {
    for(l=0;l<k;++l) {
      A[j][l]=ran(10);
    }
  }
  return A;
}

// Returns a random number from the set {0,...,9}.
int ran(int i) {
  srand((unsigned int) time(0));
  return(rand()%10);
}
+7  A: 

You can never return a stack-allocated ("auto") variable of something other than a primitive (value) type, and structs of such. For other types, you need to allocate the memory from the heap, using malloc(), or wrap the (fixed-size) array into a struct.

If you're using a fixed-size array, you can model it as a struct and use struct-return:

#define WIDTH  11
#define HEIGHT 11

typedef struct {
  unsigned char cell[WIDTH * HEIGHT];
} Board;

Board board_new(void)
{
  Board b;
  size_t i;

  for(i = 0; i < sizeof b.cell / sizeof *b.cell; i++)
    b.cell[i] = rand() & 255;
  return b;
}

This is fine, and should not be more costly than the alternative, of using an explicit pointer:

void board_init(Board *b);

Since the former case of struct-return can be rewritten (by the compiler) to the latter. This is called return value optimization.

unwind
That's just not true - you can return `struct` types, which are decidedly non-primitive. It is not a great idea, though.
caf
But struct types will be copied from the stack into a (hidden) local variable near the caller. It could work if the array was defined to be fixed-size so the compiler knows the size of the return structure/array. But it doesn't. It's a dynamic array...
Workshop Alex
malloc() isn't the only way to allocate memory. You can have static variable within a function
qrdl
Alex, there's no requirement for the array to be dynamically sized.
Michael Krelin - hacker
@caf, Alex: thanks, I forgot about struct return. Fixed now.
unwind
unwind, I'm downvoting you for the use of word 'best' where it's more than inappropriate ;-) "the best would probably be to model it as a struct". I'll revert my downvote if you change your wording;-)
Michael Krelin - hacker
hacker, the return type of the function is a dynamic array. Sure, it doesn't have to be a dynamic array but the compiler can't determine the size of the return data because the return type indicates a variable length.
Workshop Alex
unwind, took my downvote back.Alex, if you take a closer look at the code, you'll find that the data in question is [WIDTH][HEIGHT] array where WIDTH and HEIGHT are quite constant, i.e. known at compile time. And you souldn't worry about the return type in function declaration, since it's wrong anyway and was meant to illustrate the intention.
Michael Krelin - hacker
I tried this but my code has still some compilation errors. The code can be found on http://jaakkospage.comyr.com/koe.c
Jaska
@Jaska: I got it to build, by removing the ; from the #defines, and changing the order of the functions, since you don't pre-declare anything.
unwind
+8  A: 

You can't. You can either pass pointer to array as a parameter and have function modify it, or the function itself can allocate data and return pointer.

in your case

void rand_grid(char A[WIDTH][HEIGHT]) {
    A[0][0] = 'A'; // or whatever you intend to do
}

main() {
    char A[WIDTH][HEIGHT];
    rand_grid(A);
}

Edit: As caf pointed out one can actually return the struct with an array in it, but of course no c-programmer in their right mind would do that.

Michael Krelin - hacker
You can actually return the array by value by wrapping it within a `struct`, but it's a very bad idea.
caf
Thanks. I was wondering how to solve the following problem: I need a grid filled with random integers. I want that the initialization of an array is not on the main function as it would be useful in the rest of the program. How can I use pointers to solve the problem?
Jaska
Jaska, you should make this sub-question as a separate one, i.e. a new question on stackoverflow.
Frank Grimm
Provided that array dimensions are set at compile time you can use the code snippet from my answer. It is effectively a passing of pointer.
Michael Krelin - hacker
caf, that's true. But as you pointed out no c-programmer in their right mind would do that.
Michael Krelin - hacker
Why is it a bad idea to return an array within a struct? Packing small arrays into structs isn't uncommon. It is probably faster and surely easier than mallocing memory and returning a pointer to it.
Johannes Schaub - litb
litb, of course it's not that bad for smaller arrays (actually, everything that is *possible* is useful sometimes), but if it's anything of significant size I'd rather pass the pointer (or equivalent) in, instead of out. Again, I wouldn't really want to come up with universal statement here.
Michael Krelin - hacker
A: 

If you really want to do that you can try making the array A static, this way the storage for A is not determined by the scope of function and you can actually return the array(in form of pointer of course).

But this is not a good way to do accomplish what you are trying to achieve, instead pass the array to function rand_grid . Thats what pass by address is meant for.

Neeraj
There's no such thing as "pass by address" in C. I'm sure you mean `That's what passing the (value of the) array address is meant for.`
pmg
yes i meant the same..
Neeraj
+5  A: 

Several things to point out.

First of all, you cannot assign an array object as you do here:

char A[WIDTH][HEIGHT];  
A=rand_grid(WIDTH,HEIGHT);

Objects of array type are not modifiable.

Secondly, functions in C cannot return array types. They can return pointers to arrays, though:

char (*foo(int width))[HEIGHT]
{
  /**
   * dynamically allocate memory for a widthxHEIGHT array of char
   */
  char (*newArr)[HEIGHT] = malloc(sizeof *newArr * width);
  /**
   * initialize array contents here
   */
  return newArr;
}

The syntax is a little confusing; it reads as

       foo                                   -- foo
       foo(int width)                        -- is a function
                                             -- taking an int parameter
      *foo(int width)                        -- returning a pointer
     (*foo(int width))[HEIGHT]               -- to a HEIGHT-element array
char (*foo(int width))[HEIGHT]               -- of char

For C89, HEIGHT in the above snippet must be a compile-time constant integral expression (either a macro, a numeric literal, or an arithmetic expression consisting of macros and/or numeric literals). I'm not sure if that's also true for C99.

Based on the snippet you've posted, what you want to do is to take an array you've already allocated and initialize its contents. Remember that in most contexts, an expression of an array type will implicitly be converted to a pointer to the base type. IOW, if you pass an N-element array of T to a function, what the function actually receives is a pointer to T:

void foo (T *p) {...}
...
T arr[N];
foo(arr);

For 2-d arrays, it's a little uglier:

void foo (T (*p)[M]) {...}
...
T arr[N][M];
foo(arr);

This also relies on M being known at compile time, which limits the function's usefulness. What you'd like is a function that can deal with a 2-d array of arbitrary size. The best way I know of to accomplish this is instead of passing a pointer to the array, pass the address of the first element in the array[1], and pass the number of rows and columns as separate parameters:

void foo(T *base, size_t rows, size_t cols) {...}
...
T arr[N][M];
foo (&arr[0][0], N, M);

So your rand_grid function would look something like this:

void rand_grid(char *base, size_t rows, size_t cols)
{
  size_t i, j;
  for (i = 0; i < rows; i++)
  {
    for (j = 0; j < cols; j++)
    {
      /**
       * Since base is a simple char *, we must index it
       * as though it points to a 1-d array.  This works if
       * base points to the first element of a 2-d array,
       * since multi-dimensional arrays are contiguous.  
       */
      base[i*cols+j] = initial_value();
    }
  }
}

int main(void)
{
  char A[WIDTH][HEIGHT];
  rand_grid(&A[0][0], WIDTH, HEIGHT);
  ...
}


  1. Even though the expressions &A[0][0] and A yield the same value (the base address of A), the types of the two expressions are different. The first expression evaluates to a simple pointer to char (char *), while the second evaluates to a pointer to a 2-d array of char (char (*)[HEIGHT]).
John Bode