1

Related to dynamic allocation inside a function, most questions & answers are based on double pointers.

But I was recommended to avoid using double pointer unless I have to, so I want to allocate a 'array pointer' (not 'array of pointer') and hide it inside a function.

int (*arr1d) = calloc(dim1, sizeof(*arr1d));
int (*arr2d)[dim2] = calloc(dim1, sizeof(*arr2d));

Since the above lines are the typical dynamic-allocation of pointer of array, I tried the following.

#include <stdio.h>
#include <stdlib.h>

int allocateArray1D(int n, int **arr) {
    *arr = calloc(n, sizeof(*arr));
    for (int i = 0; i < n; i++) {
        (*arr)[i] = i;
    }

    return 0;
}

int allocateArray2D(int nx, int ny, int *(*arr)[ny]) {
    *arr[ny] = calloc(nx, sizeof(*arr));
    for (int i = 0; i < nx; i++) {
        for (int j = 0; j < ny; j++) {
            (*arr)[i][j] = 10 * i + j;
        }
    }

    return 0;
}

int main() {
    int nx = 3;
    int ny = 2;

    int *arr1d = NULL;                  // (1)
    allocateArray1D(nx, &arr1d);

    int(*arr2d)[ny] = NULL;             // (2)
    allocateArray2D(nx, ny, &arr2d);

    for (int i = 0; i < nx; i++) {
        printf("arr1d[%d] = %d \n", i, arr1d[i]);
    }
    printf("\n");

    printf("arr2d \n");
    for (int i = 0; i < nx; i++) {
        for (int j = 0; j < ny; j++) {
            printf(" %d ", arr2d[i][j]);
        }
        printf("\n");
    }

    return 0;
}

And the error message already comes during the compilation.

03.c(32): warning #167: argument of type "int (**)[ny]" is incompatible with parameter of type "int *(*)[*]"
    allocateArray2D(nx, ny, &arr2d);
                            ^

It is evident from the error message that it has been messed up with the argument types (that I wrote as int *(*arr)[ny]) but what should I have to put there? I tried some variants like int *((*arr)[ny]), but didn't work).

And if I remove the 2D parts, then the code well compiles, and run as expected. But I wonder if this is the right practice, at least for 1D case since there are many examples where the code behaves as expected, but in fact there were wrong or un-standard lines.

Also, the above code is not satisfactory in the first place. I want to even remove the lines in main() that I marked as (1) and (2).

So in the end I want a code something like this, but all with the 'array pointers'.

int **arr2d;
allocateArray2D(nx, ny, arr2d);

How could this be done?

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Sangjun Lee
  • 406
  • 2
  • 12
  • sure, int (*arr)[] is already a pointer to an array. However in arguments your array is supposed to contain pointers to an int. But arr2d is an array of just ints. – Serge Jan 23 '23 at 14:04
  • Two points here. 1/ as your function only return a useless value (always 0) you could return the allocated array instead... 2/ in `int allocateArray2D(int nx, int ny, int *(*arr)[ny])`, `int *(*arr)[ny]` is a Variable Length Array declaration. VLA support is optional in C since C11, so your code will not be safe for example on a Microsoft compiler. – Serge Ballesta Jan 23 '23 at 14:18
  • @SergeBallesta You can't declare a function returning pointer to VLA however, since the dimensions aren't known at compile time. You can return `void*` but then it isn't type safe. Regarding VLA support, mistakes were made in C11. They are getting fixed in C23 so that pointers to VLA become mandatory once more, but objects of VLA remains an optional feature. But then of course Microsoft doesn't follow any of the standards C89, C99, C11, C17 or C23... so you are correct that your code will not be safe if compiled on that compiler, since it isn't conforming. – Lundin Jan 23 '23 at 14:24
  • @Lundin: For the VLA part, Microsoft **is** conforming, at least since MSVC2017... I shall not say more about conformance ;-) – Serge Ballesta Jan 23 '23 at 14:30

2 Answers2

2

You need to pass the array pointer by reference (not pass an array pointer to an array of int*):
int *(*arr)[ny] -> int (**arr)[ny]

The function becomes:

int allocateArray2D(int nx, int ny, int (**arr)[ny]) {
    *arr = calloc(nx, sizeof(int[ny])); // or sizeof(**arr)
    for (int i = 0; i < nx; i++) {
        for (int j = 0; j < ny; j++) {
            (*arr)[i][j] = 10 * i + j;
        }
    }
    return 0;
}

For details, check out Correctly allocating multi-dimensional arrays

Best practices with malloc family is to always check if allocation succeeded and always free() at the end of the program.


As a micro-optimization, I'd rather recommend to use *arr = malloc( sizeof(int[nx][ny]) );, since calloc just creates pointless overhead bloat in the form of zero initialization. There's no use of it here since every item is assigned explicitly anyway.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thank you for the answer and it is very helpful. I have one additional question here, why `int (**arr)[ny]` than `int *(*arr)[ny]` or `int *((*arr)[ny])`? I thought that the laters will do the work since I was trying to pass 'a pointer to array pointer', so I enclosed things inside the `(...)`. Are there any good reason for enclosing `**` inside `(...)`, or is it something like 'grammar' I have to follow in using C 'language'? – Sangjun Lee Jan 24 '23 at 00:39
  • Also, the post you referenced is indeed very very helpful again. But again, I wonder how `void arr_fill (size_t x, size_t y, int array[x][y])` worked in the last full working code Lundin had posted. How the argument `int array[x][y]` (not `int (*array)[x][y]`) can survive outside of the function maintaining the values assigned inside the function? Maybe from so-called 'array decay' that the `int array[x][y]` interpreted as a pointer to the very first element, and it is dereferenced at `array[i][j] = ...` line into `*(*arr + y*i + j) = ...` inside `void array_fill` function? – Sangjun Lee Jan 24 '23 at 01:55
  • 1
    @SangjunLee As explained in the first sentence `int *(*arr)[ny]` declares a pointer to an array of pointers to int. Which is wrong. Whenever you want a pointer to an array or function, you surround the name with parenthesis then put the asterisk in front: `int array[n]`, pointer to: `int (*array)[n]`. Function pointers work exactly the same way too. – Lundin Jan 24 '23 at 07:26
  • 1
    @SangjunLee As for that other post, if you don't need to return a pointer to the caller like when using malloc, then there is no need for the pointer syntax. `int array[x][y]` in a function parameter list is equivalent to `int (*array)[y]` ("array decay"). So when you access the elements pointed at by that pointer, you change the data on the caller side. – Lundin Jan 24 '23 at 07:28
0
  1. Wrong parameter type
  2. Strange allocation
  3. Wrong size type
  4. I would return the array as void * too (at least to check if allocation did not fail).
void *allocateArray2D(size_t nx, size_t ny, int (**arr)[ny]) {
    //*arr = calloc(nx, sizeof(**arr)); calloc is not needed here as you assign values to the array
    *arr = malloc(nx * sizeof(**arr));
    for (size_t i = 0; i < nx; i++) {
        for (size_t j = 0; j < ny; j++) {
            (*arr)[i][j] = 10 * i + j;
        }
    }

    return *arr;
}
0___________
  • 60,014
  • 4
  • 34
  • 74