50
#include<stdio.h>
void print(int *arr[], int s1, int s2) {
    int i, j;
    for(i = 0; i<s1; i++)
        for(j = 0; j<s2; j++)
            printf("%d, ", *((arr+i)+j));
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

This works in C, but not in C++.

error:

cannot convert `int (*)[4]' to `int**' for argument `1' to 
`void print(int**, int, int)'

Why does it not work in C++? What change is needed to be made?

Robert Gamble
  • 106,424
  • 25
  • 145
  • 137
Moeb
  • 10,527
  • 31
  • 84
  • 110
  • 9
    Hi - ever thought about using STL-Libs instead of arrays? They are quite convenient in C++ and solve a lot of issues like this ;-) – Ta Sas May 13 '10 at 16:54
  • compiling as C with `gcc`, you'll get the warning `passing argument 1 of 'print' from incompatible pointer type`; also, the indexing logic is borked and only seems to work (for some values of work) as long as `sizeof (int) == sizeof (int *)` – Christoph May 13 '10 at 16:57
  • +1 for using STL containers: http://www.cplusplus.com/reference/stl/ – Robben_Ford_Fan_boy May 13 '10 at 16:59
  • 3
    Is there are reason you're doing `*((arr+i)+j))` instead of the much clearer `arr[i][j]`? – GManNickG May 13 '10 at 17:01
  • 21
    C'mon guys, answer the question. I'm sure he knows about the STL. – bobobobo May 13 '10 at 17:12
  • At the time this question was asked, I didn't know what a pointer in C or C++ was, let alone a multidimensional array. 11 years later, today, however, I feel like I have something valuable to add to what's already been said. So, [I've added this incredibly thorough and detailed answer here](https://stackoverflow.com/a/67814330/4561887) covering 4 techniques I use to pass and use multidimensional arrays. I have tested each and have gone into detail on when and how to use each technique. – Gabriel Staples Jun 04 '21 at 23:22

16 Answers16

34

This code will not work in either C or C++. An array of type int[4][4] is not convertible to a pointer of type int ** (which is what int *arr[] stands for in parameter declaration). If you managed to compile it in C, it is simply because you probably ignored a C compiler warning of basically the same format as the error message you got from C++ compiler. (Sometimes C compilers issue warnings for what is essentially an error.)

So, again, don't make assertions that are not true. This code does not work in C. In order to convert a built-in 2D array into a int ** pointer you can use a technique like this one

Converting multidimensional arrays to pointers in c++

(See the accepted answer. The problem is exactly the same.)

EDIT: The code appears to work in C because another bug in the printing code is masquerading the effects of the bug in array passing. In order to properly access an element of an int ** pseudo-array, you have to use expression *(*(arr + i) + j), or better a plain arr[i][j] (which is the same thing). You missed the extra * which made it print something that has absolutely nothing to do with the content of your array. Again, initialize your array in main to something else to see that the results you are printing in C have absolutely nothing to do with the your intended content of the array.

If you change the printf statement as shown above, your code will most likely crash because of the array-passing bug I described initially.

One more time: you cannot pass a int[4][4] array as an int ** pseudo-array. This is what the C++ is telling you in the error message. And, I'm sure, this is what your C compiler told you, but you probably ignored it, since it was "just a warning".

Community
  • 1
  • 1
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • @cambr: One more time: the code does not work. Whatever results you are getting from it in C is just garbage, which just accidentally happens to look like the correct output. Initialize your array to something else (not zeros) and try again. – AnT stands with Russia May 13 '10 at 17:09
  • Well. THe type is different, but the underlying memory address is the same. Hence it is rational to treat this as a warning for a C compiler. – kriss May 13 '10 at 17:09
  • 7
    @kriss: Er... I don't know what "the underlying memory address is the same" is supposed to mean. The physical structure of `int[4][4]` array is not even remotely similar to an `int **` pseudo-array. Any attempts to access `int[4][4]` array through a `int **` pointer normally lead to a segfault. Or to meaningless results. In this case the meanigless results accidentally happen to look as meaningful results, which is why the OP decided that the code works. – AnT stands with Russia May 13 '10 at 17:12
  • @AndreyT: I fixed the int** to int* in my answer It's definitely an error. But `&a == &a[0] == &a[0][0]` that's what I mean by underlying memory address is the same. – kriss May 13 '10 at 17:21
  • In other words, the OP did not test their broken code well enough. Hence the baseless assumption that it "works", while in reality it doesn't. – AnT stands with Russia May 13 '10 at 17:22
  • @kriss: Yes, but I don't see what relevance that fact is supposed to have in this case. – AnT stands with Russia May 13 '10 at 17:23
  • That's also only meaningful if it holds true for every value in the array- not just for the primary pointer. Adding to pointers like he has done is undefined behaviour, too. The solution with the templates is the best solution - if the OP can't use the STL. – Puppy May 13 '10 at 17:38
  • @DeadMG: not if you ever have to maintain this code, I much prefer the C99 version using variadic array. – kriss May 13 '10 at 18:47
  • @AndreyT: the relevance is that as the internal layout of arrays is sensibly defined in C standards (contiguity), when you now it, it become both simpler and faster to use 1d arrays for such cases. – kriss May 13 '10 at 18:52
  • 1
    @kriss; Well, firstly, pedantically speaking, reinterpreting 2D array as a 1D array is illegal from the standard point of view, even if the internal layout would allow that. Secondly, in order to do that you'd have to convert `int[4][4]` to `int *`, not to `int **` as in OP's case. – AnT stands with Russia May 13 '10 at 18:55
  • @Kriss: The code maintains itself. That's why it's such great code. – Puppy May 13 '10 at 21:17
  • To correct the thesis of this answer... "This code will not work in EITHER C or C++." – Jason Jul 28 '15 at 16:27
  • @Jason: I wanted to put an extra emphasis on the negation :) Corrected, thanks. – AnT stands with Russia Jul 28 '15 at 16:37
23

The problem is, that

int a[4][4];

will actually be stored in a physically continuous memory. So, to access an arbitrary part of your 4x4 array, the function "print" needs to know the dimensions of the array. For example the following little piece of code, will access the same part of the memory in two different ways.

#include <iostream>

void print(int a[][4]) {
    for (int i = 0; i <4; i++) {
        for (int j = 0; j < 4; j++) {
            //accessing as 4x4 array
            std::cout << a[i][j] <<std::endl;        

            //accessing corresponding to the physical layout in memory
            std::cout <<  *(*(a)+ i*4 + j) << std::endl;  

        }
    }
}

int main() {
    int a[4][4];

    //populating the array with the corresponding indices from 0 to 15
    int m = 0;
    for (int i = 0; i<4; i++) {
        for (int j= 0; j < 4; j++) {
            a[i][j] =  m;
            m++;
        }
    }
    print(a);
}

So the memory layout doesn't change but the way of accessing does. It can be visualized like a checkerboard.

   0  1  2  3
  ----------
0| 1  2  3  4
1| 5  6  7  8
2| 9 10 11 12
3|13 14 15 16

But the real physical memory looks like this.

0*4+0 0*4+1 0*4+2 0*4+3 1*4+0 1*4+1 1*4+2 1*4+3 2*4+1   etc.
-----------------------------------------------------
1      2       3    4     5     6      7     8     9    etc.

In c++ the data of an array is stored row-by-row and the length of a row (in this case 4) is always necessary to get to the proper memory offset for the next row. The first subscript therefore only indicates the amount of storage that is needed when the array is declared, but is no longer necessary to calculate the offset afterwards.

1MinLeft
  • 93
  • 7
Lucas
  • 13,679
  • 13
  • 62
  • 94
  • Nice explanation, but in your example code, you should define the function passing in the limit value for i, or it is dangerous to loop until 4 is reached (If I call with an a[1][4] array I will probably get a segfault, but will be ok for compiler). – kriss May 13 '10 at 18:45
  • 1
    More precisely, it needs to know the number of columns, not necessarily the number of rows. – JohnMcG May 13 '10 at 20:57
17

Note: this answer doesn't answer the specifics of the OP's question. There are already answers for that. Rather, it answers only the title of the OP's question: "How to pass a multidimensional array to a function in C and C++", since Google searches for that phrase or similar lead right here, and I have a lot to say on the topic. Keep in mind if I made my own question to post this as an answer, it would be closed as a duplicate of this question, so I'm posting this here instead.

For passing 1D arrays, see my other answer here instead: Passing an array as an argument to a function in C

If in a hurry:

...and:

  1. you are using C++, it really is best to represent a 2D array as a vector of vectors (ex: std::vector<std::vector<int>> (recommended!)), so see my other answer here: How to pass a multidimensional array to a function in C++ only, via std::vector<std::vector<int>>&.
  2. you really want to use C-style multi-dimensional arrays in C or C++, jump straight down and look at the 4 print examples under the "Summary of Conclusions and Recommendations..." section.

How to use multidimensional (ex: 2D) arrays, and pointers to them, as function parameters in C and C++

Multidimensional arrays are very confusing for just about everybody, myself included--for experienced programmers and beginners alike. So, I'd like to provide a canonical set of examples I can come back to and reference again and again (see comments below this answer, however; I don't cover it all), with some clear demonstrations and easy-to-follow guidelines. Here it goes.

Upfront notes:

  1. NB: as of C2x (C20 or later), the following "original principle" guideline 15 is in effect (source: Wikipedia: C2x --> original source: Programming Language C - C2x Charter):

    1. Application Programming Interfaces (APIs) should be self-documenting when possible. In particular, the order of parameters in function declarations should be arranged such that the size of an array appears before the array. The purpose is to allow Variable-Length Array (VLA) notation to be used. This not only makes the code's purpose clearer to human readers, but also makes static analysis easier. Any new APIs added to the Standard should take this into consideration.

    So, if you'd like to be C2x-compliant (most std C functions to date are not), then re-arrange all of my functions below to put the array size arguments before the array or pointer-to-array arguments.

  2. I began this answer as a response to this question: Passing a pointer to array to my function. But, it fits better here, so I'm putting it here instead.

  3. The code below is found in my eRCaGuy_hello_world repo here: c/array_2d_practice.c. It compiles and runs in both C and C++ (tested in C11 and C++17). See the build and run commands at the top of the source code file. I use gcc build options -Wall -Wextra -Werror for safety.

  4. My answer focuses on multidimensional 2D arrays, but could be easily extended to any number of dimensions: 3D, 4D, 5D, etc...ND arrays.

  5. I use const in my print functions since I'm just printing the arrays and not modifying them. Remove const if you ever need to modify the arrays in-place.

Array Notes to be aware of first:

1. Fixed vs unspecified dimensions:

Arrays must be fixed (specified) size on all dimensions except the 1st (outermost) dimension, which can optionally be unspecified.

// OK; 1D array with unspecified 1st (and only) dimension
int array[] = {1, 2, 3};
// OK; 2D array with unspecified 1st dimensions
int array[][2] = {{1, 2}, {3, 4}};
// NOT allowed; 2D array with both dimensions unspecified!:
// `error: array type has incomplete element type ‘int[]’`
int array[][] = {{1, 2}, {3, 4}};

2. Natural type decay of array types:

First, let me distinguish between "array types", and "pointers to array types". "Array types" are arrays, and "pointers to array types" are pointers. Pointers cannot decay to (AKA "adjust" to become) pointers, because they are already pointers. Arrays, however can and do decay to ("adjust" to become) pointers.

(1) So, here are some examples of array types, meaning they are just regular "arrays": int array_2d[][2], int* array_2d[], int array_2d[3][2]. The first is a 2D array of ints, the 2nd is a 1D array of int*, and the 3rd is a 2D array of ints.

(2) However, this is a ptr to array type, or "pointer to array": int (*array_2d)[3][2]. The ptrs to arrays always have the parenthesis around the asterisk, like this: (*), just before the square braces. That's how you can recognize them. So, the first 3 arrays just above decay to ptrs when used as arguments, whereas the last one does not, since it's already a ptr.

The principle is: when used as function parameters, all array types (but NOT ptrs to array types) decay the first dimension down into a ptr, whether that dimension's size is explicitly specified or not! So, although int arr[] (array of ints) and int * arr (pointer to an int) are NOT the same types, a function definition with either of those in it will naturally decay the 1st dimension in the array (which is the only dimension in this 1D array case) down to a pointer, resulting in type (int * arr) being passed to the function in both cases:

// accepts `int *` as `array` parameter
void my_func(int * array, size_t len) {}
// also accepts `int *` as `array` parameter, since the `int []` type 
// (array of ints) naturally decays down to type `int *` (ptr to int`).
void my_func(int array[], size_t len) {}

Taking this further, specifying the size to the first dimension on the array has no bearing on this effect. ie: it makes no difference whatsoever and is meaningless to the compiler. It simply serves as a visual indicator or type of "self documentation" to the programmer is all, that this particular function expects an array of at least this size or larger. See my answer here (Passing an array as an argument to a function in C) where I talk about this and also quote the MISRA-C standard which recommends using this feature for self-documentation purposes.

So, these are all the same as the functions above too:

// same as above: these ALL accept `int *` as the 1st parameter, and the 
// specified size here in square brackets [] has no effect on the compiler.
void my_func(int array[1], size_t len) {}
void my_func(int array[10], size_t len) {}
void my_func(int array[100], size_t len) {}

So, this is fine:

int array[10];
my_func(array); // ok; `array` naturally decays down to type `int *`

And this is fine too:

int array[10];
int * array_p = array;
my_func(array_p); // ok; is already of type `int *`

HOWEVER, for pointers to arrays, the actual array type and size DOES matter, and no natural type decay from an array to a ptr occurs because the type is already a ptr--to an array of a specified type and size! Read my answer above.

Example: the following function REQUIRES an input parameter of type ptr to 1D array of size 10. It's already a ptr, so no natural type decay to a ptr occurs! Since this parameter is a ptr to an array, you must also pass the address of the array via the & character when making the function calls, as shown below. Notice that only 2 of the calls below work: my_func(&array2); and my_func(array2_p2);. I went through the effort of showing all of these calls, however, to be able to explain and demonstrate the various array types and how and when they decay to pointers, and of what type.

// 0. Define a function

/// `array` is a "ptr to an array of 10 ints". 
void my_func(int (*array)[10]) {}

// 1. Create arrays

int array1[5];
int *array1_p = array1; // array1_p is of type `int *` (ptr to int)
int (*array1_p2)[5] = &array1; // array1_p2 is of type `int (*)[5]` (ptr
                               // to array of 5 ints)

int array2[10];
int *array2_p = array2; // array2_p is of type `int *` (ptr to int)
int (*array2_p2)[10] = &array2; // array2_p2 is of type `int (*)[10]` (ptr
                                // to array of 10 ints)

// 2. Make some calls 

// 2.1. calling with `int array1[5]`

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to **natural type decay** from
// `int[5]` (array of 5 ints) to `int *` (ptr to int)
my_func(array1);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to dereferencing to `int[5]` (array
// of 5 ints), followed by **natural type decay** from `int[5]`
// (array of 5 ints) to `int *` (ptr to int)
my_func(*array1_p2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int)
my_func(array1_p);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`; due to **natural type decay** from `int[5]` (array of
// 5 ints) to `int *` (ptr to int), in conjunction with dereferencing
// from that to `int`
my_func(*array1);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`
my_func(*array1_p);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (*)[5]` (ptr to array of 5 ints)
my_func(&array1);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (*)[5]` (ptr to array of 5 ints)
my_func(array1_p2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (**)[5]` (ptr to "ptr to array of 5 ints")
my_func(&array1_p2);

// 2.2. calling with `int array2[10]`

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to **natural type decay** from
// `int[10]` (array of 10 ints) to `int *` (ptr to int)
my_func(array2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to dereferencing to `int[10]` (array
// of 10 ints), followed by **natural type decay** from `int[10]`
// (array of 10 ints) to `int *` (ptr to int)
my_func(*array2_p2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int)
my_func(array2_p);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`; due to **natural type decay** from `int[10]` (array of
// 10 ints) to `int *` (ptr to int), in conjunction with dereferencing
// from that to `int`
my_func(*array2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`
my_func(*array2_p);

// <===============
// <=== WORKS! ====
// <===============
// Expected and received `int (*)[10]` (ptr to array of 10 ints)
my_func(&array2);

// <===============
// <=== WORKS! ====
// <===============
// Expected and received `int (*)[10]` (ptr to array of 10 ints)
my_func(array2_p2);

// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (**)[10]` (ptr to "ptr to array of 10 ints")
my_func(&array2_p2);

3. A quick reminder on pointers:

Remember that the types int *[2] and int (*)[2] are NOT the same type! The parenthesis matter! int *[2] is an "array of 2 int *s (pointers to int)", whereas int (*)[2] is a "ptr to an array of 2 ints". These are two very different things.

Also, a ptr can be indexed like an array, which leads to the very-frequent confusion that an array IS a ptr, which is FALSE. An array is NOT a ptr! But, the following concept is critical to understanding the code below: int array_2d[][2] is a 2D array. Variable array_2d is of type int [][2] (n x 2 (2D) array of ints), which is a 2D array with an unspecified number of rows (dimension 1), and 2 columns (dimension 2). When used as a function parameter, this int [][2] type naturally decays to type int (*)[2] (ptr to (1D) array of 2 ints). So, if this decayed type is a ptr, how is it still an array? Well, since a ptr can be indexed like an array, you can still do things like this to index into it: array_2d[row][col]. The outer dimension is the ptr, which is indexable as the row, while the inner dimension is the [2] (2 ints) part, which is indexable as the column since it is a sub-array within an array. This means that each row contains 2 ints, so once you index into a row, you then need to index into a column. So, the confusion between ptrs and arrays lies in the fact that all ptrs are indexable like arrays, even though arrays are NOT ptrs--rather, the first dimension of all arrays (but NOT ptrs to arrays) decays into a ptr when used as an argument.

So, with the above concepts in mind, the following will make more sense. For each function definition, pay attention to what the array type is and whether or not it will naturally decay, and to what. Again, when used as function parameters, all non-pointer array types decay the first dimension of the array down into a ptr, which is still indexable like an array.

Summary of Conclusions and Recommendations for passing multidimensional arrays:

Here are my 4 use-cases and techniques to pass multi-dimensional arrays as parameters, and recommendations on when to use each. You can see from the function prototypes and definitions for each technique the varying tradeoffs, complexities, and benefits that each offers.

Assume you have the following 2D array:

int arr[][2] =
{
    {1, 2},
    {5, 6},
    {7, 8},
};

...and the following macro definitions:

// Get the number of elements in any C array
// - from my repo here:
//   https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/utilities.h#L42
#define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))

/// Definitions: `rows` = "rows"; `cols` = "columns"

/// Get number of rows in a 2D array
#define NUM_ROWS(array_2d) ARRAY_LEN(array_2d)

/// Get number of columns in a 2D array
#define NUM_COLS(array_2d) ARRAY_LEN(array_2d[0])
  1. Example 1: Fixed-size multi-dimensional arrays: if the 2D array is ALWAYS the same size each time (it has a FIXED number of rows, and a FIXED number of columns) (3 rows and 2 columns in the below example), do this:
    // 1. Function definition
    /// \brief      Print a 2D array which has a FIXED number of rows and
    ///             FIXED number of columns.
    /// \param[in]  array_2d    a 2D array; is of type `int (*)[3][2]` (ptr to
    ///                         3 x 2 (2D) array of ints); since it is already
    ///                         explicitly a ptr, it does NOT naturally decay to
    ///                         any other type of ptr
    /// \return     None
    void print_array2(const int (*array_2d)[3][2])
    {
        printf("print_array2:\n");
        for (size_t row = 0; row < NUM_ROWS(*array_2d); row++)
        {
            for (size_t col = 0; col < NUM_COLS(*array_2d); col++)
            {
                printf("array_2d[%zu][%zu]=%i ", row, col, (*array_2d)[row][col]);
            }
            printf("\n");
        }
        printf("\n");
    }
    
    // 2. Basic usage
    // NB: `&` is REQUIRED! See my answer for why: https://stackoverflow.com/a/51527502/4561887
    print_array2(&arr);
    
    // 3. Usage via a pointer
    // `int (*array_2d)[3][2]` is an explicit ptr to a 3x2 array of `int`. This
    // pointer to an array does NOT naturally decay to a simpler type.
    int (*p2)[3][2] = &arr; // must use `&` and MUST USE THESE PARENTHESIS!
    print_array2(p2);
    
  2. Example 2: If the 2D array has a VARIABLE number of rows, but a FIXED number of columns (2 in this case), do this:
    // 1. Function definition
    /// \brief      Print a 2D array which has a VARIABLE number of rows but
    ///             FIXED number of columns.
    /// \param[in]  array_2d    a 2D array; is of type `int [][2]` (n x 2 (2D) array
    ///                         of ints), which naturally decays to type
    ///                         `int (*)[2]` (ptr to (1D) array of 2 ints)
    /// \param[in]  num_rows    The number of rows in the array
    /// \return     None
    void print_array3(const int array_2d[][2], size_t num_rows)
    {
        printf("print_array3:\n");
    
        // Technique 1: use `array_2d` directly.
        printf("--- Technique 1: ---\n");
        for (size_t row = 0; row < num_rows; row++)
        {
            for (size_t col = 0; col < NUM_COLS(array_2d); col++)
            {
                printf("array_2d[%zu][%zu]=%i ", row, col, array_2d[row][col]);
            }
            printf("\n");
        }
    
        // Technique 2: cast the `array_2d` decayed ptr to a ptr to a sized array of
        // the correct size, then use that ptr to the properly-sized array
        // directly! NB: after obtaining this ptr via the cast below, this
        // technique is **exactly identical** to (I copy/pasted it from, then
        // renamed the variable) the implementation inside `print_array2()` above!
        printf("--- Technique 2: ---\n");
        int (*array_2d_ptr)[num_rows][NUM_COLS(array_2d)] =
            (int (*)[num_rows][NUM_COLS(array_2d)])array_2d;
        for (size_t row = 0; row < NUM_ROWS(*array_2d_ptr); row++)
        {
            for (size_t col = 0; col < NUM_COLS(*array_2d_ptr); col++)
            {
                printf("array_2d_ptr[%zu][%zu]=%i ", row, col, (*array_2d_ptr)[row][col]);
            }
            printf("\n");
        }
    
        printf("\n");
    }
    
    // 2. Basic usage
    print_array3(arr, NUM_ROWS(arr));
    
    // 3. Usage via a pointer
    // `int array_2d[][2]` (n x 2 (2D) array of ints) naturally decays to 
    // `int (*)[2]` (ptr to (1D) array of 2 ints)
    int (*p3)[2] = arr; // MUST USE THESE PARENTHESIS!
    print_array3(p3, NUM_ROWS(arr));
    
  3. Example 3: If the 2D array has a VARIABLE number of rows AND a VARIABLE number of columns, do this (this approach is the most-versatile and is therefore generally my overall preferred approach, and go-to approach for multidimensional arrays):
    // 1. Function definition
    /// \brief      Print a 2D array which has a VARIABLE number of rows and
    ///             VARIABLE number of columns.
    /// \param[in]  array_2d    a 2D array; is of type `int *` (ptr to int); even
    ///                         though a 1D array of type `int []` (array of ints)
    ///                         naturally decays to this type, don't think about it
    ///                         that way; rather, think of it as a ptr to the first
    ///                         `int` in a contiguous block of memory containing a
    ///                         multidimensional array, and we will manually index
    ///                         into it as required and according to its dimensions
    /// \param[in]  num_rows    The number of rows in the array
    /// \param[in]  num_cols    The number of columns in the array
    /// \return     None
    void print_array4(const int *array_2d, size_t num_rows, size_t num_cols)
    {
        printf("print_array4:\n");
    
        // Technique 1: use `array_2d` directly, manually indexing into this
        // contiguous block of memory holding the 2D array data.
        printf("--- Technique 1: ---\n");
        for (size_t row = 0; row < num_rows; row++)
        {
            const int *row_start = &array_2d[row*num_cols];
    
            for (size_t col = 0; col < num_cols; col++)
            {
                // NB: THIS PART IS VERY DIFFERENT FROM THE OTHERS! Notice `row_start[col]`.
                printf("array_2d[%zu][%zu]=%i ", row, col, row_start[col]);
            }
            printf("\n");
        }
    
        // Technique 2: cast the `array_2d` decayed ptr to a ptr to a sized array of
        // the correct size, then use that ptr to the properly-sized array
        // directly! NB: after obtaining this ptr via the cast below, this
        // technique is **exactly identical** to (I copy/pasted it from, then
        // renamed the variable) the implementation inside `print_array2()` above!
        printf("--- Technique 2: ---\n");
        int (*array_2d_ptr)[num_rows][num_cols] =
            (int (*)[num_rows][num_cols])array_2d;
        for (size_t row = 0; row < NUM_ROWS(*array_2d_ptr); row++)
        {
            for (size_t col = 0; col < NUM_COLS(*array_2d_ptr); col++)
            {
                printf("array_2d_ptr[%zu][%zu]=%i ", row, col, (*array_2d_ptr)[row][col]);
            }
            printf("\n");
        }
    
        printf("\n");
    }
    
    // 2. Basic usage
    print_array4((int *)arr, NUM_ROWS(arr), NUM_COLS(arr));
    // OR: alternative call technique:
    print_array4(&arr[0][0], NUM_ROWS(arr), NUM_COLS(arr));
    
    // 3. Usage via a pointer
    // The easiest one by far!
    int *p4_1 = (int*)arr;
    // OR
    int *p4_2 = &arr[0][0];
    print_array4(p4_1, NUM_ROWS(arr), NUM_COLS(arr));
    print_array4(p4_2, NUM_ROWS(arr), NUM_COLS(arr));
    

If you have the following "2D" array, however, you must do something different:

// Each row is an array of `int`s.
int row1[] = {1, 2};
int row2[] = {5, 6};
int row3[] = {7, 8};
// This is an array of `int *`, or "pointer to int". The blob of all rows
// together does NOT have to be in contiguous memory. This is very different
// from the `arr` array above, which contains all data in contiguous memory.
int* all_rows[] = {row1, row2, row3}; // "2D" array
  1. Example 4: If the 2D array is actually built up of a bunch of ptrs to other arrays (as shown just above), do this:
    // 1. Function definition
    /// \brief      Print a 2D-like array, where the array passed in is an array of
    ///             ptrs (int *) to other sub-arrays. Each index into the outer
    ///             array is the row, then each index into a sub-array in a given
    ///             row is the column. This handles a VARIABLE number of rows and
    ///             VARIABLE number of columns.
    /// \details    `array_2d` here is different from all of the cases above. It is
    ///             NOT a contiguous 2D array of `int`s; rather, it is an array of
    ///             pointers to ints, where each pointer in the array can be
    ///             thought of as a sub-array. Therefore, the length of the outer
    ///             array is the number of rows, and the length of each sub-array,
    ///             or inner array, is the number of columns. Each sub-array
    ///             (a single row of `int`s) DOES have to be in contiguous memory,
    ///             and the array of _pointers_ DOES have to be in contiguous
    ///             memory, but the total _storage space_ for the combined total of
    ///             all rows can be in NON-contiguous memory. Again, this is VERY
    ///             different from every other function above.
    /// \param[in]  array_2d    a 2D array; is of type `int * []` (array of ptrs to
    ///                         int) (where each ptr is a sub-array of ints);
    ///                         `int * []` naturally decays to type `int**` (ptr to
    ///                         "ptr to int")
    /// \param[in]  num_rows    The number of rows in the array (number of elements
    ///                         in the `array_2d` outer array)
    /// \param[in]  num_cols    The number of columns in the array (number of
    ///                         elements in each sub-array)
    /// \return     None
    void print_array5(const int* array_2d[], size_t num_rows, size_t num_cols)
    {
        printf("print_array5:\n");
    
        printf("--- Technique 1: use `row_start[col]` ---\n");
        for (size_t row = 0; row < num_rows; row++)
        {
            const int *row_start = array_2d[row]; // VERY DIFFERENT FROM `print_array4` above!
            for (size_t col = 0; col < num_cols; col++)
            {
                // Identical to `print_array4` above.
                printf("array_2d[%zu][%zu]=%i ", row, col, row_start[col]);
            }
            printf("\n");
        }
    
        printf("--- Technique 2: use `array_2d[row][col]` ---\n");
        for (size_t row = 0; row < num_rows; row++)
        {
            for (size_t col = 0; col < num_cols; col++)
            {
                // OR you can simply do this!
                printf("array_2d[%zu][%zu]=%i ", row, col, array_2d[row][col]);
            }
            printf("\n");
        }
    
        printf("\n");
    }
    
    // 2. Basic usage
    print_array5(all_rows, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
    
    // 3. Usage via a pointer
    //
    // 3.1. Easier way: ptr to "ptr to int"; note: `int* array_2d[]` naturally
    // decays to `int**`.
    const int **p5_1 = all_rows;
    print_array5(p5_1, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
    //
    // 3.2. OR this more-complicated way, for the sake of demonstration:
    // ptr to array of 3 `int*`s
    const int* (*p5_2)[ARRAY_LEN(all_rows)] = &all_rows;
    // Explanation: the type of `p5_2` is `int* (*)[3]` (ptr to array of 3
    // int*), so the type of `*p5_2` is `int* [3]` (array of 3 int*), which
    // decays naturally to `int**`, which is what `*p5_2` ends up passing to
    // this function call! So, this call to `print_array5()` here and the one
    // just above are therefore exactly identical!
    print_array5(*p5_2, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
    

Don't forget about structs!

Don't forget, however, that sometimes, just using structs is so much easier!

Example:

typedef struct data_s
{
    int x;
    int y;
} data_t;

// Array of the above struct
data_t data_array[] =
{
    {1, 2},
    {5, 6},
    {7, 8},
};

void print_struct_data(data_t * data, size_t len)
{
    for (size_t i = 0; i < len; i++)
    {
        printf("[data[%zu].x, data[%zu].y] = [%i, %i]\n",
               i, i, data[i].x, data[i].y);
    }
    printf("\n");
}

print_struct_data(data_array, ARRAY_LEN(data_array));

Output:

[data[0].x, data[0].y] = [1, 2]
[data[1].x, data[1].y] = [5, 6]
[data[2].x, data[2].y] = [7, 8]

The full, runnable code:

The full, runnable code causes this answer to exceed the 30000 max chars allowed in an answer. So, download the full code here: c/array_2d_practice.c, in my eRCaGuy_hello_world repo.

Sample output (reduced; please run the full code yourself):

print_array1:
array_2d[0][0]=1 array_2d[0][1]=2
array_2d[1][0]=5 array_2d[1][1]=6
array_2d[2][0]=7 array_2d[2][1]=8

print_array2:
array_2d[0][0]=1 array_2d[0][1]=2
array_2d[1][0]=5 array_2d[1][1]=6
array_2d[2][0]=7 array_2d[2][1]=8

print_array3:
--- Technique 1: ---
array_2d[0][0]=1 array_2d[0][1]=2
array_2d[1][0]=5 array_2d[1][1]=6
array_2d[2][0]=7 array_2d[2][1]=8
--- Technique 2: ---
array_2d_ptr[0][0]=1 array_2d_ptr[0][1]=2
array_2d_ptr[1][0]=5 array_2d_ptr[1][1]=6
array_2d_ptr[2][0]=7 array_2d_ptr[2][1]=8

print_array4:
--- Technique 1: ---
array_2d[0][0]=1 array_2d[0][1]=2
array_2d[1][0]=5 array_2d[1][1]=6
array_2d[2][0]=7 array_2d[2][1]=8
--- Technique 2: ---
array_2d_ptr[0][0]=1 array_2d_ptr[0][1]=2
array_2d_ptr[1][0]=5 array_2d_ptr[1][1]=6
array_2d_ptr[2][0]=7 array_2d_ptr[2][1]=8

print_array5:
--- Technique 1: use `row_start[col]` ---
...
--- Technique 2: use `array_2d[row][col]` ---
...

Don't forget about just using structs and arrays of structs instead, which
is sometimes much easier!

[data[0].x, data[0].y] = [1, 2]
[data[1].x, data[1].y] = [5, 6]
[data[2].x, data[2].y] = [7, 8]

References:

  1. The main reference I used: my own answer containing information on "Forcing type safety on arrays in C", and how to interpret and read 1D pointers to arrays like this: int (*a)[2]: Passing an array as an argument to a function in C
  2. https://www.geeksforgeeks.org/pass-2d-array-parameter-c/

Related/See Also:

  1. [my answer] How to make a 1D PROGMEM array of 2D PROGMEM arrays - I borrowed heavily from my "Example 2" code above to write this answer where I use PROGMEM to store and read an array of 2D arrays into Flash memory on an AVR/Arduino microcontroller.
  2. [my answer] Arduino Stack Exchange: Initializing Array of structs
  3. [my answer which references this answer] Passing a pointer to array to my function
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • As a canonical answer, it does not cover C's variable length array nor the C2x principle of [the order of parameters .... should be arranged such that the size of an array appears before the array](https://en.wikipedia.org/wiki/C2x#Features) – chux - Reinstate Monica Jun 03 '21 at 01:37
  • @chux-ReinstateMonica, true. I'll leave those alone for now. I'm already 4+ hrs into this. I'll slightly modify my wording. – Gabriel Staples Jun 03 '21 at 01:38
  • 1
    Where you say "naturally decay" in relation to function parameters, the Standard terminology is *adjusted*. You can look it up in the Standard – M.M Jun 04 '21 at 01:11
  • Please explain what you mean by "non-pointer array types". All array types are not pointer types and vice versa. – M.M Jun 04 '21 at 01:11
  • @M.M, Here are non-ptr array types: `int array_2d[][2]`, `int* array_2d[]`, `int array_2d[3][2]`. The first is a 2D array of ints, the 2nd is a 1D array of `int*`, and the 3rd is a 2D array of ints. However, this is a ptr to an array, or "pointer array type" as I phrased it: `int (*array_2d)[3][2]`. So, the 3 former ones decay to ptrs when used as arguments, whereas the last one does not, since it's already a ptr. – Gabriel Staples Jun 04 '21 at 01:20
  • @M.M, I just mean "a type that is a ptr to an array" (does not decay to a ptr--is already a ptr) vs "a type that is already a ptr" (does not decay, since it's already a ptr) vs "a type that is an array" (NOT a ptr to an array) (does decay to a ptr). – Gabriel Staples Jun 04 '21 at 01:43
  • 1
    `int (*array_2d)[3][2]` is a pointer type (not an array type). I think it is confusing to describe it as "pointer array type", since it is not an array type. Many beginners fail to distinguish between arrays and pointers and that kind of wording doesn't help. You can say "pointer to array type" . – M.M Jun 04 '21 at 03:23
  • @M.M, good point. I've updated my wording to say "pointer to array type." I think that's clearer, as you said. – Gabriel Staples Jun 04 '21 at 03:45
9
#include<stdio.h>
void print(int arr[][4], int s1, int s2) {
    int i, j;
    printf("\n");
    for(i = 0; i<s1; i++) {
        for(j = 0; j<s2; j++) {
            printf("%d, ", *((arr+i)+j));
        }
    }
    printf("\n");
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

This will work, where by work I mean compile. @AndreyT explained why your version doesn't work already.

This is how you should pass a 2d array.

For clarity, you can also specify both sizes in the function declaration:

#include<stdio.h>
void print(int arr[4][4], int s1, int s2) {
    int i, j;
    printf("\n");
    for(i = 0; i<s1; i++) {
        for(j = 0; j<s2; j++) {
            printf("%d, ", *((arr+i)+j));
        }
    }
    printf("\n");
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

Both will work.

You should also change *((arr+i)+j) to either a[i][j] (preferably) or *(*(arr+i)+j) if your intention is to access the jth element of row i.

IVlad
  • 43,099
  • 13
  • 111
  • 179
  • ok. But what's the point in passing s1 and s2 if they are fixed constants defined in function header ? You can go the full way and replace them by 4. – kriss May 13 '10 at 17:07
  • @kriss: ask the OP, I only made his program work :). One reason would be to print only a subset of the array. – IVlad May 13 '10 at 17:09
  • 1
    @|V|lad I understand you fix (see my answer), but I believe he is trying to write a general purpose print function working for any array, so I proposed another one. – kriss May 13 '10 at 17:15
  • There's no point in passing array sizes if they are already fixed at compile time. It is a question that one should answer first. If you need a function for 2D array of fixed size, don't pass the sizes. If you need a function that works with 2D arrays of any size, use the proper array passing technique. Your variants strikes me as a weird mix of the two. I don't see the point of doing it that way. – AnT stands with Russia May 13 '10 at 17:50
  • @AnT, the point is that you can use subscript notation like `a[i][j]` instead of calculated offsets like `*(*(arr+i)+j)`. This is very handy in C when working with matrices, because it makes it much easier to port matrix code from higher-level tools (like Matlab, Python, etc.), which use matrix subscripts. It is frequently necessary when working with VR, robotics, OpenGL, and many other domains that use a 3D transform matrix. Different libraries can all be made to understand a 2D array, incl. C++, and Python test loops can be optimized into C loops that are imported with ctypes. – Derek Simkowiak Sep 02 '15 at 03:51
7

Here's a version which is both working, but theoretically invalid (see below) C90 and C++98:

#include <stdio.h>

static void print(int *arr, size_t s1, size_t s2)
{
    size_t i, j;
    printf("\n");
    for(i = 0; i < s1; i++) {
        for(j = 0; j < s2; j++) {
            printf("%d, ", arr[i * s2 + j]);
        }
    }
    printf("\n");
}

int main(void) {
    int a[4][4] = {{0}};
    print(a[0], 4, 4);
    return 0;
}

A C++ version using templates (adapted from Notinlist's answer) could look like this:

#include <iostream>
#include <cstring>

using namespace std;

template <size_t N, size_t M>
struct IntMatrix
{
    int data[N][M];
    IntMatrix() { memset(data, 0, sizeof data); }
};

template <size_t N, size_t M>
ostream& operator<<(ostream& out, const IntMatrix<N,M>& m)
{
    out << "\n";
    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            out << m.data[i][j] << ", ";
        }
    }
    out << "\n";
    return out;
}

int main()
{
    IntMatrix<4,4> a;
    cout << a;
    return 0;
}

Alternatively, you could use nested STL containers - ie vector< vector<int> > - instead of a plain array.

With C99, you could do

static void print(size_t s1, size_t s2, int arr[s1][s2]) {
    printf("\n");
    for(size_t i = 0; i < s1; i++) {
        for(size_t j = 0; j < s2; j++) {
            printf("%d, ", arr[i][j]);
        }
    }
    printf("\n");
}

and call it as

print(4, 4, a);

As Robert pointed out in the comments, the first snippet actually involves undefined behaviour. However, assuming that pointer arithmetics will always result in a pointer even when undefined behaviour is involved (and not blow up your computer), there is only a single possible result because of other restrictions within the standard, ie this is an instance of where the standard leaves something unnecessarily undefined.

As far as I can tell, substituting

print(a[0], 4, 4);

with

union m2f { int multi[4][4]; int flat[16]; } *foo = (union m2f *)&a;
print(foo->flat, 4, 4);

will make it legal C.

Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • 1
    @Christoph: nice to show off C99 variadic arrays, they are not yet very well known by C programmers. – kriss May 13 '10 at 17:18
  • 1
    The first version invokes undefined behavior as the object to which arr points is the 4 element array of int and you index outside that bound in your printf call. Despite the fact that you know that the memory must be adjacent it is technically illegal, this is to allow for implementations which may check array bounds. See Question #16 and the response at http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_017.html for the details. – Robert Gamble May 13 '10 at 17:48
  • @Christoph: Subscripting is defined in terms of accessing elements within a single array object. You cannot use subscripting on a pointer that points into an array to access an element outside of that array. This is all clearly spelled out in c99 6.5.6p8. The defect report I referenced clarifies how a pointer to an element in a multidimensional array is handled. In your example, arr points to the first int in an array of 4 ints, attempting to use the pointer to access an element outside of this array is undefined, even though it will *probably* work as expected on every implementation. – Robert Gamble May 13 '10 at 18:29
  • @Robert: as function parameter is defined as `int*` there is no problem with the function internals. The only problem is if compiler will accept `a[0]` to be of the same type as `int*` at function call. – kriss May 13 '10 at 18:36
  • @Robert: Q16 does not apply because `print()` never sees a multi-dimensional array; undefined behaviour might occur because pointer arithmetics is defined only within a given array (see C99 6.5.6), so technically, you are correct; practically, this doesn't matter, though, because `(int *)foo + offset == (char *)foo + offset * sizeof (int)` and a `char *` may point anywhere within the multi-dimensional array; I'd consider this a bug within the standard – Christoph May 13 '10 at 18:38
  • @kriss: I'm sorry but I don't understand what you are saying. @Christoph: It is not what print() "sees" that is the problem, the compiler/runtime could well be tracking the assignment to the pointer all the way into the function and determine that an out-of-bounds exception has occurred inside the function, the standard allows implementations to do this. I also don't think your `char *` example is any more valid than the `int *` example, the `char *` pointer can point anywhere within one of the arrays but you still cannot use it with pointer arithmetic to access memory outside that array. – Robert Gamble May 13 '10 at 19:38
  • But again I will acknowledge that this will probably work on any implementation you are likely to encounter, it just isn't guaranteed. – Robert Gamble May 13 '10 at 19:43
  • @Robert: a `char *` can cross the sub-array boundaries if it's derived from the original multi-dimensional array in accordance with C99 6.3.2.3 §7; one could also argue that in principle, you can always create a `int (*)[16]` pointing to the `int [4][4]` to circumvent the undefined pointer arithmetics; afaik, this is legal as the conversion from `int (*)[4][4]` to `int (*)[16]` doesn't violate any alignment requirements – Christoph May 13 '10 at 19:56
  • @Christoph: Okay, I see what you are saying about the char pointer pointing to the entire array of arrays and agree that it can traverse that entire object which includes all the sub-arrays. This special provision for character pointers does not extend to other types though. Lastly, `int (*)[4][4]` and `int (*)[16]` are certainly not compatible types and such a conversion is not allowed. – Robert Gamble May 13 '10 at 20:24
  • @Robert: I will try to be simple. If you define a function with header `void f(int * i)` that implies there is no boundary checking on i. The compiler/runtime can't be tracking the assignment to the pointer as it would defeat compilation unit purpose (to do that you would have to break C calling convention). That implies using a compiler generating code another can't link to. There was such compilers in the past (I even used a C compiler with Pascal calling convention). But what's the point of making a compiler standard compliant if you can't link with it's code ? – kriss May 13 '10 at 20:32
  • @kriss: There isn't a *single* C calling convention but several common, incompatible ones and it isn't difficult to produce code with different compilers that cannot be linked. But despite any reasons you may come up with as to why an implementation might not want to perform bounds checking by carrying extra type information around, the fact is that the standard does allow it. – Robert Gamble May 13 '10 at 20:50
  • @Robert: I wonder where you did read that ? You seems to be saying that standard states that cast operator does not work any-more. If you define a 2d array `int a[5][5]` then cast it like `(int *)a` I understand the standard as allowing you to access the full 5x5 cells blocks, guaranteed contiguous block of 25 int, etc. Boundary checks if any should be performed outside the aggregate. Are you implying that C99 changed the semantic of casting ? As far as I can read `int (*)[4][4]` can be cast to `int (*)[16]` or to `int*` for that matter, it is allowed with precise semantic. – kriss May 13 '10 at 21:52
  • 1
    @kriss: You can't just cast anything you want and get a defined result, that do you think would happen if you tried to cast a `float` to `int *`? The standard defines which conversions (a cast is an explicit conversion) are valid (have defined behavior) in section 6.3 (6.3.2.3 for pointers). An implementation may support additional conversions but it doesn't have to. The conversions you specified are invalid and if you try it out with a decent compiler it should tell you as much (gcc gives *assignment from incompatible pointer type* for example). – Robert Gamble May 13 '10 at 22:37
  • @Robert: rules for casting pointers are clear in standard, the rules are about pointer *alignement*, and gcc gives the above warning only if I use implicit cast. With explicit cast to `int*` you won't get warning (and this is exactly why explicit cast is for). On this point I totally agree with @Christoph, rules of pointer aithmetic, array contiguity, explicit casting, etc. implies specific behavior. For instance array contiguity is ensured by 6.5.3.4 : you can compute array length using `sizeof array / sizeof array[0]`. There is just no room for additional runtime information. – kriss May 13 '10 at 23:58
  • @kriss: There is no such thing as an implicit cast or an explicit cast. All casts are explicit conversions. You can cast whatever you want but that doesn't mean that you are going to get a useful or defined result when you try to access the the data through the pointer. I have provided plenty of references to the standard, committee responses on the topic, and (in my answer) links to the comp.lang.c FAQ that addresses the same topic. You seem to have made up your mind despite the facts so I don't think anything I say is going to convince you otherwise. – Robert Gamble May 14 '10 at 01:24
  • @Robert: did you ever bothered to *read* the material you provided link to. I suggest rereading the following in your link. `Q16: The standard refers to a pointed-to object. There does not appear to be any concept of a slice of an array being an independent object.` Here, you are just using `authority argument` and hide behind standard to state things that just aren't there (or avoid seeing things that are). But indeed this is not the place to speak of that, I shall open a full question on it. – kriss May 14 '10 at 08:33
  • @Robert: correct me if I'm wrong, but 6.3.2.3 §7 seems to imply that as long as you don't violate alignment requirements, any object pointer conversion is a priori valid as long as no other constraints are violated (eg the aliasing rules provided by 6.5 §7); this means converting `int (*)[4][4]` to `int (*)[16]` via explicit cast is allowed; as 6.3.2.3 §7 says nothing about indirection, you'll have to check 6.5.3.2 for what happens during subscription of the latter: according to §4, indirection only yields undefined behaviour 'If an invalid value has been assigned to the pointer'... – Christoph May 14 '10 at 08:41
  • ...which is not the case here, making `int a[4][4] = {{0}}; (*(int (*)[16])&a)[7] = 42;` legal – Christoph May 14 '10 at 08:46
  • on further thought, `*(int (*)[16])&a` does violate the aliasing rules, making the statement illegal; apart from that, all behaviour is defined; you can work around this via type punning with a `union` – Christoph May 14 '10 at 09:03
  • @Robert: see last edit for a version which - as far as I can tell - is legal C – Christoph May 14 '10 at 09:17
6

You can use int** instead. Its much more flexible:

#include <stdio.h>
#include <stdlib.h>
void print(int **a, int numRows, int numCols )
{
  int row, col ;
  for( int row = 0; row < numRows; row++ )
  {
    for( int col = 0; col < numCols ; col++ )
    {
      printf("%5d, ", a[row][col]);
    }
    puts("");
  }
}

int main()
{
  int numRows = 16 ;
  int numCols = 5 ;
  int **a ;

  // a will be a 2d array with numRows rows and numCols cols

  // allocate an "array of arrays" of int
  a = (int**)malloc( numRows* sizeof(int*) ) ;

  // each entry in the array of arrays of int
  // isn't allocated yet, so allocate it
  for( int row = 0 ; row < numRows ; row++ )
  {
    // Allocate an array of int's, at each
    // entry in the "array of arrays"
    a[row] = (int*)malloc( numCols*sizeof(int) ) ;
  }

  int count = 1 ;
  for( int row = 0 ; row < numRows ; row++ )
  {
    for( int col = 0 ; col < numCols ; col++ )
    {
      a[row][col] = count++ ;
    }
  }

  print( a, numRows, numCols );
}

Another thing which you may be interested in is a structure like D3DMATRIX:

typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;

D3DMATRIX myMatrix ;

The sweet thing about this little tidbit is you can use both myMatrix.m[0][0] (to access the first element), or you can use myMatrix._11 to access that same element as well. The union is the secret.

bobobobo
  • 64,917
  • 62
  • 258
  • 363
  • Important note and distinction for anyone reading this answer: `int**` is a good approach, depending on what you are doing and how you are building your multidimensional array, but it does NOT expect nor work with a multidimensional array which is a _single, contiguous block of memory._ In that case, use [one of my fist 3 approaches here](https://stackoverflow.com/a/67814330/4561887). My 4th approach is the one which uses `int**`, or "array of ptrs to the starting element of other arrays". – Gabriel Staples Jun 04 '21 at 19:24
2
#include<cstdio>
template <size_t N, size_t M>
struct DataHolder
{
    int data[N][M];
    DataHolder()
    {
       for(int i=0; i<N; ++i)
           for(int j=0; j<M; ++j)
               data[i][j] = 0;
    }
};

template <size_t N, size_t M>
void print(const DataHolder<N,M>& dataHolder) {
    printf("\n");
    for(int i = 0; i<N; i++) {
        for(int j = 0; j<M; j++) {
            printf("%d, ", dataHolder.data[i][j]);
        }
    }
    printf("\n");
}

int main() {
    DataHolder<4,4> a;
    print(a);
}
Notinlist
  • 16,144
  • 10
  • 57
  • 99
  • Preventing unnecessary copy. Secondly if the size is fixed at compile time it should be a template parameter or a define, instead of "magic number 4 everywhere". – Notinlist May 13 '10 at 17:05
  • is there no such thing as template abuse ? ;-) – kriss May 13 '10 at 17:06
  • @Notinlist: nothing will be copied anyway. I agree with the size being a define / constant, but I think you're overcomplicating this. – IVlad May 13 '10 at 17:08
  • 1
    @IVlad. It is easily possible :-). The whole thing should be approached in an entirely different way (see STL comment at question), so this answer remains awkward anyway. :-) – Notinlist May 13 '10 at 17:11
  • It's definitely far preferable to the OP's original solution, although it could still be improved by using std::cout over printf. – Puppy May 13 '10 at 17:35
  • +1: this is a perfectly valid solution; the initialization code could be shortened to `memset(data, 0, sizeof data)` and you might want to make `print()` a method or add `operator<<()` for C++-style printing – Christoph May 13 '10 at 17:42
1

Aside from using variable-length arrays in C99, you can't really portably write a function to accept a multidimensional array if the sizes of the arrays are not known at compile-time, See Question 6.19 of the C-FAQ. The best way to handle this is to simulate multidimensional arrays using dynamically allocated memory. Question 6.16 does a very good job of explaining the details of doing this.

Robert Gamble
  • 106,424
  • 25
  • 145
  • 137
  • Looking at the [4 running examples I have in my answer](https://stackoverflow.com/a/67814330/4561887), which seem to contradict what you're saying, it seems your statement here is not correct, right? `you can't really portably write a function to accept a multidimensional array if the sizes of the arrays are not known at compile-time`. – Gabriel Staples Jun 04 '21 at 01:56
  • @GabrielStaples Of your "4 running examples", only one of them (#3) addresses multidimensional arrays where the "sizes of the arrays are not known at compile-time". This example employs two different techniques, the first one which results in undefined behavior as described in the C-FAQ 6.19 that I linked to in my answer (and thus is not portable) and the second which employs variable-length arrays (which I specifically address in my answer) so I'm not sure how any part of your answer contradicts what I wrote. – Robert Gamble Jun 05 '21 at 01:33
0
#include<stdio.h>
void print(int (*arr)[4], int s1, int s2) {
    int i, j;
    for(i = 0; i<s1; i++)
        for(j = 0; j<s2; j++)
            printf("%d, ", arr[i][j]);
}

int main() {
    int a[4][4] = {{6}};
    print(a,4,4);
}

this will compile edit: someone already posted this solution my bad

devmabbott
  • 69
  • 2
0

Short answer, you may change the program as following

void print(int arr[], int s1, int s2) {
...
printf("%d,", *(a+i + s2*j));
...
print((int*)a,4,4);

This would need a better answer explaining the differences between pointer and pointer arithmetic and arrays in C and C++. I won't launch into that now. Maybe someone else ?

I obviously am not shocked by the same point as other posters in your code. What bother me most in the print function header is that you use a double indirection for an array where you do not intend to change initial pointer back (in fact it can't be done as it is a constant). @|V|lad answer fix this by setting one or two dimensions to a fixed constant, but then passing s1 and s2 become useless.

All depends of what you really want to do. Is print a general purpose array printing function or a specialized one for some array types ?

kriss
  • 23,497
  • 17
  • 97
  • 116
  • this won't work if he changes `*((arr+i)+j)` to `arr[i][j]` however. And if he doesn't, I'm getting weird (and different) results if I init `a[i][j] = i + j` with both your and my solution. So yeah, I dunno what the OP is trying to do... – IVlad May 13 '10 at 17:22
  • Won't work if he changes his printing to `*(*(arr+i)+j)` either. WILL work if he changes to `*((arr+i)+s1*j)` however. Basically if I understand this right, you're using a 1d array to represent a 2d array... – IVlad May 13 '10 at 17:29
  • @|V|lad: yes, you are right. Yes, representing 2d or higher dimension arrays with 1d array is an old common C trick. That's the same logic that when you copy a structure using memcpy. That is ??? (what is the equivalent of "pythonic" for C) because C was designed like a very low level language, a kind of portable assembly. The drawback is that doing so you lose some type checking. C99 added some cool alternatives like variadic arrays, and you also have the C++ way of doing things. But I feel that going into the high level side, C++ has lost some of this low levelness of C. – kriss May 13 '10 at 18:22
  • `int *` isn't compatible with `int (*)[4]` (which is what `a` decays to); instead of the cast, use either the expressions `a[0]` or `&a[0][0]` in the call to `print`, both of which are compatible with `int *`. – John Bode May 13 '10 at 20:37
  • @John: that's the point, `int*` remove type checking. Hence the print function can be used with non [][4] arrays. I agree removing type checking is dangerous, but it's not forbidden (and following argument with Robert above, I wonder now if using `a[0]` could not lead to problems like boundary restriction when trying to access `a[1]` inside function, even if I know of no compiler doing that, if feels like a Java thing). – kriss May 13 '10 at 22:10
0

First thing to do is get the types right. If C++'s rules are the same as C's with respect to array types (I'm pretty sure they are), then given the declaration

int a[4][4];

the expression a has type int [4][4], which is implicitly converted ("decays") to a pointer type of int (*)[4] (pointer to 4-element array of int) when passed to print, so you need to change print to

void print(int (*arr)[4], int s1, int s2)
{
  int i, j;        
  for(i = 0; i<s1; i++)        
    for(j = 0; j<s2; j++)        
      printf("%d, ", arr[i][j]);        
}        

The expression arr[i] implicitly dereferences arr, so you don't need to mess with an explicit dereference.

The drawback is that print can only handle Nx4 arrays of int; if you want to handle other array sizes, you'll need to take a different approach.

One thing you can do is instead of passing the array, pass the address of the first element, and have print manually compute the offsets, as so:

int main() {                    
  int a[4][4] = {{0}};                    
  print(&a[0][0],4,4);  // note how a is being passed                  
}  

void print(int *arr, int s1, int s2)  // note that arr is a simple int *
{
  int i, j;
  for (i = 0; i < s1; i++)
    for (j = 0; j < s2; j++)
      printf("%d, ", arr[i * s2 + j]);
}
John Bode
  • 119,563
  • 19
  • 122
  • 198
0

Multidimensional arrays are continuous blocks of memory. So you can do it this way:

#include <stdio.h>

void pa(const int *a, int y, int x)
{
    int i, j;
    for (i=0;i<y;i++)
    {
        for (j=0;j<x;j++)
            printf("%i", *(a+j+i*x));
        printf("\n");
    }
}

int main()
{
    int a[4][3] = { {1,2,3},
                    {4,5,6},
                    {4,5,6},
                    {7,8,9} };

    pa(a[0], 4, 3);

    return 0;
}

It also works in C++;

it-west.net
  • 383
  • 1
  • 4
  • 8
0

As all(most) of the systems today is Row Major Ordered.

Here is a self-explanatory code, which takes the benefit of the concept that multi-dimensional arrays are also stored serially.

Sample working code:

#include<bits/stdc++.h>
using namespace std;

void print1DArray(int arr[]){
    cout << "1D array print: " << arr[3] << endl;
}

int get1DIndex(int r, int c, int noOfColumns){
    return r*noOfColumns + c;
}

void print2DArray(int *mat, int noOfColumns){
    cout << "\n-------------------------------\n";
    cout << "1D index generated: " << get1DIndex(1, 2, noOfColumns); // prints 5
    cout << endl << "Inside printAllPaths: " << mat[get1DIndex(1, 2, noOfColumns)];  // prints 5
}



int main(){

    int arr[5] = {0, 1, 2, 3, 4};
    int mat[3][3] = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    
    print1DArray(arr);
    print2DArray(*mat, 3);

    return 0;
}
Om Sao
  • 7,064
  • 2
  • 47
  • 61
0

Youre code does not work in C, though it compiles with warnings. The basic problem is that you're passing the wrong type of pointer as the argument to print, so you get a warning. Your element calculation is also bogus.

You can rewrite it as

#include<stdio.h>
void print(int s1, int s2, int arr[s1][s2]) {
    int i, j;
    for(i = 0; i<s1; i++)
        for(j = 0; j<s2; j++)
            printf("%d, ", arr[i][j]);
}

int main() {
    int a[4][4] = {{0}};
    print(4,4,a);
}

and it will work, though only if you compile it as C. If you try to compile with a C++ compiler, it will not work.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
0

If you really need your code to run in C too, you'll either need to:

  1. Follow my other really stinking long and really thorough answer about C-style arrays, here, or:
  2. Build up your own multi-dimensional array containers in C, from scratch.

However, if using C++, using a vector of vectors really is best for this! Hence, my new answer here:

How to pass a multidimensional array to a function in C++ only, via std::vector<std::vector<int>>&

If using C++, use a std::vector<> of std::vector<>s!:

// Example:
std::vector<std::vector<int>> array2d;

NB: see and run the full demo code used below in my eRCaGuy_hello_world repo here: array_2d_via_std_vector.cpp.

Example 1: pass it by reference: std::vector<std::vector<int>>&:

// Version 2: demonstrate passing the type withOUT the `array2d_t` type above,
// and iterating via range-based for loops.
void print_array2d_2(const std::vector<std::vector<int>>& array2d)
{
    // Option 2: range-based for loops

    // Iterate over all rows
    size_t i_row = 0;
    for (std::vector<int> row : array2d)
    {
        // Iterate over all columns
        size_t i_col = 0;
        for (int val : row)
        {
            printf("array2d[%zu][%zu]=%i ", i_row, i_col, val);
            i_col++;
        }
        i_row++;
        printf("\n");
    }
    printf("\n");
}

You can also typedef the type or use the using declaration to make the type easier to manage:

using array2d_t = std::vector<std::vector<int>>;
using grid_t = array2d_t; // alternative alias for a 2D array

// OR with typedef:
typedef std::vector<std::vector<int>> array2d_t;
typedef array2d_t grid_t; // alternative alias for a 2D array

Then pass it as array2d_t& or grid_t& or whatever. Here's another example:

// Version 1: demonstrate passing using the `array2d_t` above, and iterating
// using regular for loops.
void print_array2d_1(const array2d_t& array2d)
{
    // Option 1: regular for loops

    // Iterate over all rows
    for (size_t i_row = 0; i_row < array2d.size(); i_row++)
    {
        // To get access to just a row, do this if you like
        // const std::vector<int>* row = &array2d[i_row];
        // row->size();

        // Iterate over all columns
        for (size_t i_col = 0; i_col < array2d[i_row].size(); i_col++)
        {
            printf("array2d[%zu][%zu]=%i ",
                i_row, i_col, array2d[i_row][i_col]);
        }
        printf("\n");
    }
    printf("\n");
}

Generating arrays

Generating the arrays can be done at construction time:

    // a regular 2 x 3 2D grid or array
    array2d_t array2d_1 = {
        {11, 12, 13},
        {14, 15, 16},
    };
    print_array2d_1(array2d_1);
    print_array2d_2(array2d_1);
    printf("-------------\n\n");

Using a vector of vectors (std::vector<std::vector<int>>) also has the neat benefit of allowing you to have non-uniform multi-dimensional arrays. Ex: an array with 3 rows, and 3 columns in the first and last row, but 4 columns in the middle row:

    // Notice that having varying numbers of columns in each row in
    // `std::vector<>`-based 2D arrays is fine!
    array2d_t array2d_2 = {
        {1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10},
    };
    print_array2d_1(array2d_2);
    print_array2d_2(array2d_2);
    printf("-------------\n\n");

You can also pre-size the array to have a certain number of rows and columns at construction, so that you can then populate it via the [][] operators after construction, like this:

    constexpr size_t NUM_ROWS = 3;
    constexpr size_t NUM_COLS = 4;
    // By default, this creates a `NUM_ROWS x NUM_COLS` array with all zeros in
    // it, since that is the default value for type `int` when called as a
    // constructor-like default initializer, as in `int()`, which
    // performs "value initialization". See my Q:
    // https://stackoverflow.com/q/72367123/4561887
    array2d_t array2d_4(NUM_ROWS, std::vector<int>(NUM_COLS));
    // Let's just fill in a few of the values with non-zeros, as an example
    array2d_4[0][0] = 1;
    array2d_4[1][1] = 2;
    array2d_4[2][2] = 3;
    array2d_4[2][3] = 4;
    // array2d_4[3][3] = 5; // UNDEFINED BEHAVIOR! OUT OF ROW BOUNDS! Results
    //                      // in: `Segmentation fault (core dumped)`
    print_array2d_1(array2d_4);
    print_array2d_2(array2d_4);
    printf("-------------\n\n");

Here's what the last printed output looks like from the code just above:

array2d[0][0]=1 array2d[0][1]=0 array2d[0][2]=0 array2d[0][3]=0
array2d[1][0]=0 array2d[1][1]=2 array2d[1][2]=0 array2d[1][3]=0
array2d[2][0]=0 array2d[2][1]=0 array2d[2][2]=3 array2d[2][3]=4

array2d[0][0]=1 array2d[0][1]=0 array2d[0][2]=0 array2d[0][3]=0
array2d[1][0]=0 array2d[1][1]=2 array2d[1][2]=0 array2d[1][3]=0
array2d[2][0]=0 array2d[2][1]=0 array2d[2][2]=3 array2d[2][3]=4

-------------

Just remember: using the brace operator ([]; AKA: operator[]) does NOT automatically allocate new space for a new member if you index outside the array. Rather, that is undefined behavior and will result in a segmentation fault! See here: https://en.cppreference.com/w/cpp/container/vector/operator_at (emphasis added):

Unlike std::map::operator[], this operator never inserts a new element into the container. Accessing a nonexistent element through this operator is undefined behavior.

Use .resize() to change the size, or .push_back() to auto-grow the array as you add new elements to it.

References

  1. See the full demo code used above in my eRCaGuy_hello_world repo here: array_2d_via_std_vector.cpp
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
-3

I just want to show C++ version of bobobobo's answer.

int numRows = 16 ;
int numCols = 5 ;
int **a ;

a = new int*[ numRows* sizeof(int*) ];
for( int row = 0 ; row < numRows ; row++ )
{
   a[row] = new int[ numCols*sizeof(int) ];
}

The rest of code is the same with bobobobo's.

Cloud Cho
  • 1,594
  • 19
  • 22