Best way to pass a 2d array to functions which size is unknown at compile time in pure C
Type-wise, it is ok to go from void pointers to array pointers and back. As long as the "effective type" is an int array of the specified size. Void pointers do however have non-existent type safety, so they should be avoided for that reason.

The best way is rather to use an array and let the compiler "adjust" it to an array pointer between the lines:

void fill (size_t rows, size_t cols, int arr[rows][cols], int val);


The VLA syntax requires that rows and cols exist, so arr must be declared on the right side of them in the parameter list. Please note that this is still a pointer and not a whole VLA passed by value - we can't pass arrays by value in C.

Fixed example:

#include &lt;stdio.h&gt;

void fill(size_t rows, size_t cols, int arr[rows][cols], int val);
void print(size_t rows, size_t cols, int arr[rows][cols]);

int main(void) {
  size_t iArr = 5, jArr = 3;
  int arr[iArr][jArr];
  fill(iArr, jArr, arr, 0x45);
  print(iArr, jArr, arr);
  return 0;
}

void fill(size_t rows, size_t cols, int arr[rows][cols], int val)
{
  for(size_t i = 0; i &lt; rows; i++)
  {
    for(size_t j = 0; j &lt; cols; j++)
    {
      arr[i][j] = val;
    }
  }
}

void print(size_t rows, size_t cols, int arr[rows][cols])
{
  for(size_t i = 0; i &lt; rows; i++)
  {
    for(size_t j = 0; j &lt; cols; j++)
    {
      printf("%3d ", arr[i][j]);
    }
    putchar('\n');
  }
}