Declaring 2d array as argument of function in .h file.

Pages: 12
Hi!

How can I declare a function prototype in .h file that receives a 2d array with variable dimensions?

Here what I tried.

test.h
1
2
// this 2d array will have lines and cols defined when I run the program
void showmatrix(int games[lines][cols]);


test.c
1
2
3
4
5
#include "test.h"
void showmatrix(int games[lines][cols])
{
//...
}


The error message I'm getting:
test.h:25:26: error: ‘lines’ undeclared here (not in a function); did you mean ‘link’?
void showgames(int games[lines][cols]);
^~~~~
link
test.h:25:33: error: ‘cols’ undeclared here (not in a function)
void showgames(int games[lines][cols]);
lines doesn’t have to be there, and cols has to be a constant I’m pretty sure like '34'.

Edit: or maybe you can just go void showmatrix(int games[][]);?
Last edited on
I tried that but look what I've got:

In file included from main.c:1:
test.h:25:20: error: array type has incomplete element type ‘int[]’
void showmatrix(int games[][]);
^~~~~
test.h:25:20: note: declaration of ‘games’ as multidimensional array must have bounds for all dimensions except the first
In file included from sort.c:1:
test.h:25:20: error: array type has incomplete element type ‘int[]’
void showmatrix(int games[][]);
^~~~~
test.h:25:20: note: declaration of ‘games’ as multidimensional array must have bounds for all dimensions except the first
sort.c:51:20: error: array type has incomplete element type ‘int[]’
void showmatrix(int games[][])
What about putting it with constants? Like games[31][12]?
vectors don't have this problem.
single dimensional can be used to avoid the issue as well.
a user defined type (struct with the data, rows, and cols all inside) would do it too.

standard c++ does not allow arrays with variable dimensions. if they were constants (and in scope) it would work as well.

vectors are the best way.

but if you insist... we can take an axe to the problem and go C on it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;

void foo(int *p, int r, int c)
{
  int (*p2d)[c] = (int (*)[c]) p; //ugly cast from 1-d back to 2-d
  for(int i = 0; i < r; i++)
  {
  for(int j = 0; j < c; j++)
  cout << p2d[i][j] << " ";	   //this and the cast above are the important parts
  cout << endl;
  }
	
}

int main()
{
 int x[3][4];
 int * ip = &(x[0][0]);  //this by the way is how you can collapse to 1-d 
//if its a solid block.  int ** with news won't be a solid block. 
 for(int i = 0; i < 12; i++) //12 is rows * cols
	ip[i] = i;

foo(ip,3,4);
 
 
}


again, this is very ugly. I am A++ with pointer magic and I would have to stop and look at this a bit to be able to read, understand, and modify it, and its the simplest code example of it there is. Its error prone, hard to read, hard to edit, …can't stress enough that this is not really the way to do things in c++ (anymore). And it may even be marginal if it were C code.
Last edited on
but if you insist...

Actually I don't. I just thought would be a good and c standard solution to that.

What about putting it with constants? Like games[31][12]?

I did that. That's works fine!
But is this a good solution for c?
constants work fine, so long as they are of a fixed size and you won't need to change it. The original said variable, so I showed you the variable way. As I said above, global named constants are a good answer in that case (rather than magic numbers).

C has 4 solutions.
constants (can vary first dimension)
user defined type
pointer magic
collapse to 1-d and index manually (just do it all in 1-d)

it really depends on what you need which is best.
each one has flaws.
-constants are fixed in size, so that won't work for every problem
-user defined type requires managing it. if it has dynamic memory, that becomes more complex, and C's structs are a little clunky to work with at times. Its clean, but its a lot of work and way too much work for small problems.
- pointer magic is hard to read and needs a deep understanding of what you are doing or you will never find and fix any bugs. However note that you can call foo with 2,6 or 12,1 for the rows and cols and its fine. Its powerful.
- collapse to 1-d requires ugly indexing math at every turn, in exchange for a clean flexible solution.

and a 5th solution is double pointer dynamic memory, which is annoying to allocate and free, is not a solid block so you lose some flexibility (can't memcpy or similar single loop iterations), and is generally one of my least favorite things to have to deal with.

are you fully doing in C?
Last edited on
The original said variable, so I showed you the variable way.

But is variable.
Look:
test.h
1
2
// I've changed a little bit the suggestion of "highwayman".
void showmatrix(int games[][1], size_t lines, size_t cols);


test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
// lines and cols can have any size.
void showmatrix(int games[][1], size_t lines, size_t cols)
{	
	for(size_t i = 0; i < lines; i++) {
		for(size_t j = 0; j < cols; j++) {
			if(games[i][j] < 10)
				printf("0%d  ", games[i][j]);
			else
				printf("%d  ", games[i][j]);
		}
		printf("\n");
	}
}

// lines and cols can have any size.

No lines and cols must be no larger than the sizes of the array. In the example shown cols can be no larger than 1.
Templates maybe will help you, you could make lines and cols template arguments
Why would that help?

I like the "pointer magic" approach myself.

For genuinely variable dimensions you might mean both variable length of each dimension and a variable number of dimensions.

"Pointer magic" really isn't so much about pointers or magic, it's just math.

The array subscript syntax is more magic than reality. For example,

1
2
3
int a[3][3];

a[2][1] = 0;


Here we see the standard 2D array, a 3 x 3 matrix. The location defined by a[2][1] is easily recognized as row 2, column 1 (starting at zero of course).

However, since we know the rows are 3 integers long each, we know that the integer at row 1, column 0 is just after the last column of the previous row. So

1
2
3
int a[9];

a[ 2 * 3 + 1 ] = 0;


Is effectively identical to the previous example. It is a bit clearer this way:

1
2
3
4
5
6
int a[9];

int r = 2;
int c = 1;

a[ r * 3 + c ] = 0;


This treats the single dimension array as if it were a 3x3 2D matrix.

So, any block of memory could be used as a multi-dimensional array of any arbitrary organization of rows and columns (and pages, and chapters, etc) at will.

Once you lose the dependency on the convenience of the syntax for 2D or nD arrays from C, it becomes trivial to fashion functions which mutate as required even at runtime.

With a bit of practice and familiarity, the nD syntax of C begins to look cumbersome by comparison.

Nothing drives this home quite as well as when working with images. Uncompressed images tend to look like 2D arrays of color information.

They are rarely ever declared using the 2D array syntax of C, but of strides, like the middle examples I posted where a simple array (or block of RAM) is merely decoded by rows and columns (or x and y coordinates) based on the width of the image.

This continues to echo throughout graphics, where arrays of 3D points are intermixed with data representing normals, and the entire format is handled with strides (the length of each group), which then looks like a 1D array of structures, but the structures are not defined as a class, but by distances from the beginning of each group.



Last edited on
@jilb Games is still a regular array, so it’s size is constant, so you can just pass the dimensions as templates. it has the desired effect of his showmatrix(int games[][1], int lines,int cols){... code only looking a tiny bit different, (showmatrix<int lines,int cols> (int games[lines][cols]){... )
Last edited on
@highwayman, template arguments must be known at compile-time.
(And templates aren't in the C language.)

I second the suggestion to use a 1d array + strides (pointer math).
Last edited on
How can I declare a function prototype in .h file that receives a 2d array with variable dimensions?

Use a 2D std::vector, if you can. Or a faux 2D vector with a 1D vector using pointer math to simulate the 2 dimensions.

A 2D vector is a vector of vectors.

Creating a 2D vector with known row and column sizes is not that complicated. The vector(s) retains its size even when passed into a function, unlike a C style array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <vector>
#include <numeric>  // for std::iota, used to fill a vector with consecutive values

void Print2DArray(std::vector<std::vector<int>> const&);

int main()
{
   std::cout << "Creating a 2-dimensional vector,\nenter row size: ";
   int row_size;
   std::cin >> row_size;

   std::cout << "Enter column size: ";
   int col_size;
   std::cin >> col_size;

   std::cout << "\n";

   // create a 2 dimensional int vector with known dimensions
   std::vector<std::vector<int>> a2DVector(row_size, std::vector<int>(col_size));

   // initialize the vector with some values other than zero
   int start  = 101;
   int offset = 100;

   // step through each row and fill the row vector with some values
   for (auto& itr : a2DVector)
   {
      std::iota(itr.begin(), itr.end(), start);
      start += offset;
   }

   // let's display the filled 2D vector
   std::cout << "Displaying the filled 2D vector:\n";
   Print2DArray(a2DVector);
}


void Print2DArray(std::vector<std::vector<int>> const& a2DVec)
{
   for (size_t row_itr = 0; row_itr < a2DVec.size(); row_itr++)
   {
      for (size_t col_itr = 0; col_itr < a2DVec.at(row_itr).size(); col_itr++)
      {
         std::cout << a2DVec.at(row_itr).at(col_itr) << ' ';
      }
      std::cout << '\n';
   }

   /*for (const auto& row_itr : a2DVec)
   {
      for (const auto& col_itr : row_itr)
      {
         std::cout << col_itr << ' ';
      }
      std::cout << '\n';
   }*/
}
Creating a 2-dimensional vector,
enter row size: 3
Enter column size: 4

Displaying the filled 2D vector:
101 102 103 104
201 202 203 204
301 302 303 304

Thanks mbozzi/jilb, sry to get off track with c.
Idea for the strides/spans/1d thingy: traversing the array

1
2
3
4
5
6
7
8
9
10
11
void showmatrix(int games[],int lines, int cols){
  for(int i = 0;i < (lines*cols);i++){
    if(i > lines && i % lines == 0){
      printf("\n");
    }
    if(games[i] < 10){
      printf("0");
    }
    printf("%d "games[i]);
  }
}
the foo function is able to vary both dimensions, its only limitation is that its tied to solid blocks, as are the other 1-d under the hood approaches. Its doing the exact same things, really, just uses the odd syntax casting on the front end rather than manual indexing on the back end, allowing you to return to [][] notation if you want it. Once you get it, you see that ip in main wasn't necessary. Just pass in &array[0][0] to the function calls.
Once you make a struct with the rows, cols, and both pointers (1d and 2d) and it would clean up your functions to just passing struct references or pointers around. which works with any of the techniques mentioned …. then your parameter passing woes go completely away...

First of all thanks for your patience with me. I don't know if is my bad english or I'm newbie but maybe I intended to ask one thing and asked another.

I'm saying this because I found a solution of which none of you suggested. It does what I want and works fine. Now I don't know if is a bad solution like (post Aug 23) or what.

test.h
void showmatrix(size_t lines, size_t cols, int games[lines][cols]);

test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void showmatrix(size_t lines, size_t cols, int games[lines][cols])
{
	for(size_t i = 0; i < lines; i++) {
		for(size_t j = 0; j < cols; j++) {
			if(games[i][j] < 10)
				printf("0%d  ", games[i][j]);
			else
				printf("%d  ", games[i][j]);
		}
		printf("\n");
	}
}

/* I define the number of lines and cols when I run the program that's why I said in original post "variable dimensions".
I can run in the terminal command "./program 3 4"  or  "./program 9 9" or "./program 8 2" and so on.*/
void foo(size_t lines, size_t cols)
{
	int matrix[lines][cols];
	// code here using the 2d array
	showmatrix(lines, cols, matrix);
}
Last edited on
I'm saying this because I found a solution of which none of you suggested.

It probably wasn't suggested because this is primarily a C++ forum, your solution is a C solution that is only truly supported when using the C99 C standard.

I define the number of lines and cols when I run the program

IMO if you really want runtime length on arrays you should be using dynamic memory allocation (malloc()/free()) to allocate the memory for your arrays. Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void printArray(int **my_array, size_t rows, size_t cols);

int main()
{
    size_t rows = 3;
    size_t cols = 4;
    int **my_array = malloc(sizeof(*my_array) * rows);
    for (size_t i = 0; i < rows; i++) {
        my_array[i] = malloc(sizeof(*my_array[i]) * cols);
    }

    printArray(my_array, 3, 4);
    // Don't forget to free the memory you allocated.
    for (size_t i = 0; i < rows; i++) {
        free(my_array[i]);
    }
    free(my_array);
}

void printArray(int **my_array, size_t rows, size_t cols)
{
    for(size_t i = 0; i < rows; i++) {
		for(size_t j = 0; j < cols; j++) {
			if(my_array[i][j] < 10)
				printf("0%d  ", my_array[i][j]);
			else
				printf("%d  ", my_array[i][j]);
		}
		printf("\n");
	}


}
I suppose c99 is 20 years old and going strong, though. No reason to not use it if you want to.
Pages: 12