Pass 2D-arrays to functions in C

 Section 26.1: Pass a 2D-array to a function

Passing a 2d array to a functions seems simple and obvious and we happily write:

        #include <stdio.h>

        #include <stdlib.h>

        #define ROWS 3

        #define COLS 2

        void fun1(int **, int, int);

        int main()

        {

                int array_2D[ROWS][COLS] = { {1, 2}, {3, 4}, {5, 6} };

                int n = ROWS;

                int m = COLS;

                fun1(array_2D, n, m);

                return EXIT_SUCCESS;

        }

        void fun1(int **a, int n, int m)

        {

                int i, j;

                for (i = 0; i < n; i++) 

                {

                    for (j = 0; j < m; j++) 

                    {

                            printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                    }

                }

        }


The reasons for this are twofold: the main problem is that arrays are not pointers and the second inconvenience is the so called pointer decay. Passing an array to a function will decay the array to a pointer to the first element of the array--in the case of a 2d array it decays to a pointer to the first row because in C arrays are sorted row-first.

        #include <stdio.h>

        #include <stdlib.h>

        #define ROWS 3

        #define COLS 2

        void fun1(int (*)[COLS], int, int);

        int main()

        {

                int array_2D[ROWS][COLS] = { {1, 2}, {3, 4}, {5, 6} };

                int n = ROWS;

                int m = COLS;

                fun1(array_2D, n, m);

                return EXIT_SUCCESS;

        }

        void fun1(int (*a)[COLS], int n, int m)

        {

            int i, j;

            for (i = 0; i < n; i++) 

            {

                for (j = 0; j < m; j++) 

                {

                    printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                }

            }

        }

It is necessary to pass the number of rows, they cannot be computed.

        #include <stdio.h>

        #include <stdlib.h>

        #define ROWS 3

        #define COLS 2

        void fun1(int (*)[COLS], int);

        int main()

        {

            int array_2D[ROWS][COLS] = { {1, 2}, {3, 4}, {5, 6} };

            int rows = ROWS;

            /* works here because array_2d is still in scope and still an array */

            printf("MAIN: %zu\n",sizeof(array_2D)/sizeof(array_2D[0]));

            fun1(array_2D, rows);

            return EXIT_SUCCESS;

        }

        void fun1(int (*a)[COLS], int rows)

        {

            int i, j;

            int n, m;

            n = rows;

            /* Works, because that information is passed (as "COLS").

            It is also redundant because that value is known at compile time (in "COLS"). */

            m = (int) (sizeof(a[0])/sizeof(a[0][0]));

            /* Does not work here because the "decay" in "pointer decay" is meant

            literally--information is lost. */

            printf("FUN1: %zu\n",sizeof(a)/sizeof(a[0]));

            for (i = 0; i < n; i++) 

            {

                for (j = 0; j < m; j++) 

                {

                    printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                }

            }

        }

The number of columns is predefined and hence fixed at compile time, but the predecessor to the current C-standard (that was ISO/IEC 9899:1999, current is ISO/IEC 9899:2011) implemented VLAs (TODO: link it) and although the current standard made it optional, almost all modern C-compilers support it (TODO: check if MS Visual Studio supports it now).

        #include <stdio.h>

        #include <stdlib.h>

        /* ALL CHECKS OMMITTED!*/

        void fun1(int (*)[], int rows, int cols);

        int main(int argc, char **argv)

        {

            int rows, cols, i, j;

            if(argc != 3)

            {

                fprintf(stderr,"Usage: %s rows cols\n",argv[0]);

                exit(EXIT_FAILURE);

            }

            rows = atoi(argv[1]);

            cols = atoi(argv[2]);

            int array_2D[rows][cols];

            for (i = 0; i < rows; i++) 

            {

                for (j = 0; j < cols; j++) 

                {

                    array_2D[i][j] = (i + 1) * (j + 1);

                    printf("array[%d][%d]=%d\n", i, j, array_2D[i][j]);

                }

            }

            fun1(array_2D, rows, cols);

            exit(EXIT_SUCCESS);

        }

        void fun1(int (*a)[], int rows, int cols)

        {

            int i, j;

            int n, m;

            n = rows;

            /* Does not work anymore, no sizes are specified anymore

            m = (int) (sizeof(a[0])/sizeof(a[0][0])); */

            m = cols;

            for (i = 0; i < n; i++) 

            {

                    for (j = 0; j < m; j++) 

                    {

                        printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                    }

            }

        }

It becomes a bit clearer if we intentionally make an error in the call of the function by changing the declaration to void fun1(int **a, int rows, int cols). That causes the compiler to complain in a different, but equally nebulous way:-

We can react in several ways, one of it is to ignore all of it and do some illegible pointer juggling:

        #include <stdio.h>

        #include <stdlib.h>

        /* ALL CHECKS OMMITTED!*/

        void fun1(int (*)[], int rows, int cols);

        int main(int argc, char **argv)

        {

            int rows, cols, i, j;

            if(argc != 3)

            {

                fprintf(stderr,"Usage: %s rows cols\n",argv[0]);

                exit(EXIT_FAILURE);

            }

            rows = atoi(argv[1]);

            cols = atoi(argv[2]);

            int array_2D[rows][cols];

            printf("Make array with %d rows and %d columns\n", rows, cols);

            for (i = 0; i < rows; i++) 

            {

                for (j = 0; j < cols; j++) 

                {

                    array_2D[i][j] = i * cols + j;

                    printf("array[%d][%d]=%d\n", i, j, array_2D[i][j]);

                }

            }

            fun1(array_2D, rows, cols);

            exit(EXIT_SUCCESS);

        }

        void fun1(int (*a)[], int rows, int cols)

        {

                int i, j;

                int n, m;

                n = rows;

                m = cols;

                printf("\nPrint array with %d rows and %d columns\n", rows, cols);

                for (i = 0; i < n; i++) 

                {

                    for (j = 0; j < m; j++) 

                    {

                        printf("array[%d][%d]=%d\n", i, j, *( (*a) + (i * cols + j)));

                    }

                }

        }

Or we do it right and pass the needed information to fun1. To do so wee need to rearrange the arguments to fun1: the size of the column must come before the declaration of the array. To keep it more readable the variable holding the number of rows has changed its place, too, and is first now.

        #include <stdio.h>

        #include <stdlib.h>

        /* ALL CHECKS OMMITTED!*/

        void fun1(int rows, int cols, int (*)[]);

        int main(int argc, char **argv)

        {

            int rows, cols, i, j;

            if(argc != 3)

            {

                fprintf(stderr,"Usage: %s rows cols\n",argv[0]);

                exit(EXIT_FAILURE);

            }

            rows = atoi(argv[1]);

            cols = atoi(argv[2]);

            int array_2D[rows][cols];

            printf("Make array with %d rows and %d columns\n", rows, cols);

            for (i = 0; i < rows; i++) 

            {

                for (j = 0; j < cols; j++) 

                {

                    array_2D[i][j] = i * cols + j;

                    printf("array[%d][%d]=%d\n", i, j, array_2D[i][j]);

                }

            }

            fun1(rows, cols, array_2D);

            exit(EXIT_SUCCESS);

        }

        void fun1(int rows, int cols, int (*a)[cols])

        {

            int i, j;

            int n, m;

            n = rows;

            m = cols;

            printf("\nPrint array with %d rows and %d columns\n", rows, cols);

            for (i = 0; i < n; i++) 

            {

                for (j = 0; j < m; j++) 

                {

                    printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                }

            }

        }

This looks awkward to some people, who hold the opinion that the order of variables should not matter. That is not much of a problem, just declare a pointer and let it point to the array.

        #include <stdio.h>

        #include <stdlib.h>

        /* ALL CHECKS OMMITTED!*/

        void fun1(int rows, int cols, int **);

        int main(int argc, char **argv)

        {        

            int rows, cols, i, j;

            if(argc != 3)

            {

                fprintf(stderr,"Usage: %s rows cols\n",argv[0]);

                exit(EXIT_FAILURE);

            }

            rows = atoi(argv[1]);

            cols = atoi(argv[2]);

            int array_2D[rows][cols];

            printf("Make array with %d rows and %d columns\n", rows, cols);

            for (i = 0; i < rows; i++) 

            {

                for (j = 0; j < cols; j++) 

                {

                    array_2D[i][j] = i * cols + j;

                    printf("array[%d][%d]=%d\n", i, j, array_2D[i][j]);

                }

            }

            // a "rows" number of pointers to "int". Again a VLA

            int *a[rows];

            // initialize them to point to the individual rows

            for (i = 0; i < rows; i++) 

            {

                a[i] = array_2D[i];

            }

            fun1(rows, cols, a);

            exit(EXIT_SUCCESS);

        }

        void fun1(int rows, int cols, int **a)

        {

            int i, j;

            int n, m;

            n = rows;

            m = cols;

            printf("\nPrint array with %d rows and %d columns\n", rows, cols);

            for (i = 0; i < n; i++) 

            {

                for (j = 0; j < m; j++) 

                {

                    printf("array[%d][%d]=%d\n", i, j, a[i][j]);

                }

            }

        }


Section 26.2: Using flat arrays as 2D arrays

Often the easiest solution is simply to pass 2D and higher arrays around as flat memory.


        /* create 2D array with dimensions determined at runtime */

        double *matrix = malloc(width * height * sizeof(double));

        /* initialise it (for the sake of illustration we want 1.0 on the diagonal) */

        int x, y;

        for (y = 0; y < height; y++)

        {

            for (x = 0; x < width; x++)

            {

                if (x == y)

                matrix[y * width + x] = 1.0;

                else

                matrix[y * width + x] = 0.0;

            }        

        }

        /* pass it to a subroutine */

        manipulate_matrix(matrix, width, height);

        /* do something with the matrix, e.g. scale by 2 */

        void manipulate_matrix(double *matrix, int width, int height)

        {

            int x, y;

            for (y = 0; y < height; y++)

            {

                for (x = 0; x < width; x++)

                {

                    matrix[y * width + x] *= 2.0;

                }

            }

        }


Post a Comment

0 Comments