views:

111

answers:

5

I took a hiatus from C and am just getting back into it again.

If I want to create a 2D array of doubles, I can do it two ways:

double** m_array = (double**) malloc(2*sizeof(double*));
double*  m_array = (double*)  malloc(2*sizeof(double));

OR

double array[2][2];

But, when I wish to pass the malloc'd array versus passing the other, there seems to be two conventions:

//allowed for passing in malloc'd array, but not for other array
func_m(m_array) //allowed
func_m(array) //disallowed
func_m(double** m_array)

//allowed for passing in either array; required for passing in non-malloc'd array
func(m_array) //allowed
func(array) //allowed
func(double array[][2])

In the first, I don't need any information beyond that it is a pointer to an array of pointers. But it can only be a malloc'd array.

In the second, I need to pass the length of each array that the array of double* points to. This seems silly.

Am I missing something? Thanks in advance.

+6  A: 

The first one doesn't create a 2-D array at all. It creates an array of pointers, which apparently point nowhere. If you did initialize each pointer to be an array, that would still be an array of arrays, not a 2-D array.

Why don't you just create a 2-D array?

double* dynamicArray = malloc(rows * columns * sizeof (double));

or

double autoArray[rows][columns];

and then you can use either one with this function:

void func(double* array, size_t rows, size_t columns);
Ben Voigt
I rescind my check-mark because your code won't compile. The double* array variable DOES need something to tell it how long the columns are so the compiler can compute array[row][col] correctly (for **BOTH** the malloc and static case). See my attempted answer below.
dougvk
@dougvk: That's why my example shows passing both the number of rows and of columns, which are all the information needed to compute the correct offset for any `(i, j)` coordinates within the array.
Ben Voigt
I understand that, but the compiler doesn't. Also, I was asking *why* that needed to be done (because I was hoping to skip that requirement in my code).
dougvk
The array of arrays method allows the address of any element to be computed without knowing the bounds, but I don't see how that's an advantage. If the function doesn't know the bounds, it might access memory outside the array. The reason you have to pass the bounds separately is that in C you don't pay for what you don't use. If the caller and called function had some other way of agreeing on the size, it would be a waste to store the size in the array. Of course you can make a structure to hold the pointer and bounds and pass them as a unit, if you want them to be a unit.
Ben Voigt
Also, the array-of-arrays will be slower. Memory dereference is slower than the offset calculation, and memory locality for the array-of-arrays is much worse as well.
Ben Voigt
Terminology: what you call a 2D array is in fact an array of arrays (each element of the outer array is an array itself); the other construction is an array of pointers to arrays. There is not really a true multidimensional array type in C.
jilles
With C99, you can do `void func(size_t rows, size_t columns, double array[rows][columns]);` and use it in the usual way. If the array is dynamically allocated you will probably need a `*` (which has no run-time effect) to make the "array of arrays of double" type from a pointer. With C89, you are stuck with manual multiplication to find the element.
jilles
@jilles: Thanks for mentioning the use of prior arguments in C99, that's pretty cool. I don't know why you call `dynamicArray` an array of arrays, because there's only one allocation, one bunch of contiguous elements, hence one array. The 2-D aspect is created by the offset calculation involving two coordinates to find the right element within the one single array.
Ben Voigt
@Ben Voigt: I didn't say it was an advantage, I just wanted an answer as to why you needed to specify the column with static, but not with dynamic. I learned a lot from your answer, but ended up exploring the a bit more in depth than in your response (explained in my answer). Thanks for your help, though!
dougvk
A: 

Your allocation for the 2D array is wrong. The usual 2D way is

double **m_array = malloc(NROWS * sizeof *m_array);
for (i = 0; i < NROWS; i++) {
  m_array[i] = malloc(NCOLS * sizeof *m_array[i]);
}

When passing arrays or pointers (any dimensions) to functions you have to let the function know the dimensions: they are not encoded within the array or pointers themselves.

On one hand, you have pointers (to pointers (to pointers (...))) to some and on the other hand you have arrays (of arrays (of arrays (...))) of some type.

When you pass a pointer to a function, that's exactly what happens. When you "pass an array", what happens is that the array decays into a pointer to its first element. So

foo(array); // is the same as foo(&(array[0]));

and when array is a multidimensional array the type of the decayed pointer is pointer to array[size] of SOMETHING (but you can't use that size!).

The easy way out is to write your functions with a simple pointer and pass the multiplication of all dimensions

double array[3][4][5];
foo(&(array[0][0][0]), 3*4*5);
pmg
yes, this still doesn't get around not having to pass the dimension. Why does C require that?
dougvk
That still isn't a 2-D array, it's a (possibly jagged) array of arrays.
Ben Voigt
A: 

For clarification, suppose you declare:

int my_static_array[row][col];

What is the type of my_static_array vs the malloc'd array (let's call it my_malloc array) in pmg's answer? my_static_array is a contiguous row*col*sizeof(int) number of bytes in memory, with an int* pointer to its beginning. When computing my_static_array[x][y], the compiler knows to do (because they are stored in row-major order):

*(addr(my_static_array) + [sizeof(int)*col*x] + [sizeof(int)*y])

This is why I need to pass in the col value for C:

(1) func(int my_static_array[][col])
(2) func(int my_static_array[][])

In (1), the compiler knows how to compute the address properly for the static array. In (2), the compiler does not have enough information. This is why (1) compiles, and (2) will not.

If it is the malloc'd array, however, one can pass:

func(int** my_malloc_array)

Since it is a pointer to a contiguous array of 1D arrays, the compiler needs no help. my_malloc_array[x][y] is done simply:

*(*(addr(my_malloc_array)+x)+y)

The compiler needs no other information about its dimensionality in order to compute.

So, what is the lesson? 2D arrays are NOT the same as an array of pointers to 1D arrays. The former has type int (*my_static_array)[row*col] -- pointer to an array of row*col elements, the latter has type int** -- pointer to an array of row pointers.

dougvk
A: 

Suppose the computer memory is a warehouse full of sequentially numbered boxes. When you do

int matrix[10][3];

you reserve 30 boxes for holding matrix values (let's say boxes 131 to 160 inclusive).

Now, lets say you want to sum all the values in the matrix in a function:

int matrix_sum(int matrix[10][3]) { /* ... */ }

but all the function receives (mandated by the Standard) is a pointer to the first element of matrix: a pointer to an array of 3 ints. So the function knows boxes 131, 132, and 133 belong to the matrix, but it doesn't know how long the matrix really is.

pmg
A: 

Use a simple typedef:

typedef double Twodouble_t[2];

void func(Twodouble_t *ptr)
{
  size_t i, numelements = sizeof*ptr/sizeof**ptr;
  puts("first array");
  for( i=0; i<numelements; ++i )
    printf("\n%f",ptr[0][i]);
}
...
Twodouble_t array[]={ {1., 1.},{2., 2.},{3.,3.} };
func(array);
...