0

I have been able to come up with following ways of sending a 2-d array to the function:

#include <stdio.h>

# define NUM_TWO_DIM_ROWS 3
# define NUM_TWO_DIM_COLS 5

void iterate_two_dim(int[][NUM_TWO_DIM_COLS]);
void iterate_two_dim1(int (*)[NUM_TWO_DIM_COLS]);
void iterate_two_dim2(int *);

int main() {

    int two_dim[][NUM_TWO_DIM_COLS] = { //note the second dimension needs to be specified to resolve expression like two_dim[1]
                        {1,2,3,4,5},    // second dimension tells how many integers to move the two_dim pointer
                        {6,7,8,9,10},
                        {11,12,13,14,15}
                    };

    iterate_two_dim(two_dim);
    iterate_two_dim1(two_dim);
    iterate_two_dim2(*two_dim);


}

void iterate_two_dim(int two_dim[][NUM_TWO_DIM_COLS]) { //function parameter uses array notation
    printf("Two dim array passed using array notation\n" );
    for(int row = 0; row < NUM_TWO_DIM_ROWS; row++) {
        for(int col = 0; col < NUM_TWO_DIM_COLS; col++) {
            printf("two_dim[%d][%d] = %-4d  ", row,col, two_dim[row][col] );
        }
        printf("\n");
    }
    printf("\n");
}

void iterate_two_dim1(int (*two_dim)[NUM_TWO_DIM_COLS]) { //function parameter uses pointer notation
    printf("Two dim array passed using pointer notation\n" );
    for(int row = 0; row < NUM_TWO_DIM_ROWS; row++) {
        for(int col = 0; col < NUM_TWO_DIM_COLS; col++) {
            printf("two_dim[%d][%d] = %-4d  ", row,col, two_dim[row][col] );
        }
        printf("\n");
    }
    printf("\n");
}

void iterate_two_dim2(int *two_dim) { //function parameter uses pointer notation
    printf("Two dim array passed using pointer notation\n" );
    char buffer[100];
    for(int count = 0; count < NUM_TWO_DIM_ROWS * NUM_TWO_DIM_COLS; count++) {
            if(count > 0 && count % NUM_TWO_DIM_COLS == 0 )
                printf("\n");
            snprintf(buffer, 40, "two_dim[%d] = %2d", count, two_dim[count] );
            printf("%-20s", buffer );
        }
        printf("\n");
}

Any other ways one can think of for this code where array two_dim is declared and initialized as shown?

2
  • @Ruks no you can't. Commented Feb 19, 2019 at 6:08
  • 1
    Yes, you could pass a pointer to the whole array. (Can you figure out the type?) Another thing is that you could use `sizeof(*two_dim)/sizeof(int) for the number o columns in the one-dimensional array case. Commented Feb 19, 2019 at 6:09

2 Answers 2

4

The ways 1 and 2 are the same (and the correct ones). An argument of array of type is adjusted to an argument of type pointer to type, i.e. int[][NUM_TWO_DIM_COLS] becomes int (*two_dim)[NUM_TWO_DIM_COLS] after the adjustment.


The way 3 is wrong. You're accessing an array out of bounds. The compiler is allowed to take into the account that the pointer points to the first element of an array of NUM_TWO_DIM_COLS and do bounds-checking against this bound.

C11 Appendix J.2 lists this as undefined behaviour:

An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).

Sign up to request clarification or add additional context in comments.

8 Comments

@PeterA.Schneider yes, undefined behaviour due to accessing array out of bounds.
I think that the case is subtly different here. iterate_two_dim2(*two_dim); passes a one-dimensional array, namely the first row, which is adjusted to a simple pointer to int (reflected in the declaration of the parameter as int *two_dim. For such pointers the compiler can generally not make bounds assumptions. (That becomes more obvious if we imagine the function definition in a different translation unit.) Note that in your quote the wrong indexing uses the same variable -- in that case the compiler can and should check, at least for constant indices.
The question then becomes whether one can consider a two-dimensional array a homogeneous memory surface that can be incrementally iterated. I think yes but I'm not perfectly sure with respect to the language. Factually all ints are adjacent per language definition (that needed not be the case if we had structs). And all computed offsets are within the object. Don't Cuda and similar SIMD mechanisms use pointers to the first element of multi-dimensional arrays to pass data?
the last one could be fixed with iterate_two_dim2((int *)&two_dim)
@PeterA.Schneider the thing is: C guarantees the memory layout. What it doesn't guarantee is that you can use a certain pointer to iterate this memory layout.
|
1

A fourth way would be to pass a typed pointer to the whole array, as I alluded in a comment. I didn't test the code but you'll get the idea. Note that *two_dim_ptr is the whole 2D-array, and sizeof(*two_dim_ptr) is the number of all elements times the size of int (it's one of the few cases where an array expression doesn't decay to a pointer).

**two_dim_ptr is the first element of the 2-D array, a one-dimensional array with NUM_TWO_DIM_COLS elements. Consequently, sizeof(*two_dim_ptr)/sizeof(**two_dim_ptr) computes the number of rows.

***two_dim_ptr is the first row's first element, here an int; Consequently, sizeof(**two_dim_ptr)/sizeof(***two_dim_ptr) computes the number of elements in a row.

Computing the index borders using the size of the elements has the maintenance advantage that the code doesn't need to change if you change the element type or constant names. The downside is that it is harder to read.

void iterate_two_dim_p(int (*two_dim_ptr)[NUM_TWO_DIM_ROWS][NUM_TWO_DIM_COLS]) { //function parameter is pointer to array of specific size
    printf("True pointer to two dim array passed\n" );
    for(int row = 0; row < sizeof(*two_dim_ptr)/sizeof(**two_dim_ptr); row++) {
        for(int col = 0; col < sizeof(**two_dim_ptr)/sizeof(***two_dim_ptr); col++) {
            printf("two_dim_ptr[%d][%d] = %-4d  ", row,col, two_dim_ptr[row][col] );
        }
        printf("\n");
    }
    printf("\n");
}

You'd call it with iterate_two_dim_p(&two_dim);, that is you take the address of the whole array. (The position of argument to the adress operator is another case where arrays do not decay. The result is a properly typed address of a 2-D array, not just the address of its first row. Of course all addresses are numerically identical because the address of a composite type is the address of its first element, a rule which applies recursively, so that (size_t)&two_dim == (size_t)two_dim && (size_t)two_dim == (size_t)*two_dim. The difference is in the types.)

2 Comments

Thank you for such an insightful answer, especially the last part: Of course all addresses are numerically identical because the address of a composite type is the address of its first element, a rule which applies recursively, so that (size_t)&two_dim == (size_t)two_dim && (size_t)two_dim == (size_t)*two_dim. The difference is in the types.)
@abc Glad you liked it. I thought it was a good answer and wondered why it didn't appear to receive any attention.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.