0

I need to pass Two Dimension array to a function as a single pointer. There are different types of approaches are there but due to some constraints(CodeGeneration), I want to pass a single pointer only. I have macros which contain the size of each dimension. I implemented the following way but I am not sure it will work fine for N dimensions also

#define size_1D 3
#define size_2D 3

void fun(int *arr)
{
   int i,total_size = size_1D* size_2D;
   for(i = 0; i < total_size ; i++)
   {
      int value = arr[i];
   }
}
int main()
{
    int arr[size_1D][size_2D] = {{1,2,7},{8,4,9}};
    fun(&arr[0][0]);
}

Any loophole is there if I followed the above approach?

2
  • 1
    The primary loophole is that you've not specified the size of the array in the argument list to the function. If you only deal with fixed size arrays, that's tolerable. However, since each separate fixed size requires its own set of functions to manipulate the array, it is painful to work with multiple array sizes, whereas a single general-purpose function that is passed the array dimensions could do the job for all array sizes. Commented Jan 24, 2019 at 7:13
  • Actually I will call only once this function so no need to make it generic for my application Commented Jan 24, 2019 at 7:42

1 Answer 1

4
void fun(int (*arr)[3]);

or exactly equivalent, but maybe more readable:

void fun(int arr[][3]);

arr is a pointer to two dimensional array with 3 rows and 3 columns. arr decayed to a pointer has the type of a pointer to an array of 3 elements. You need to pass a pointer to an array of 3 elements. You can access the data normally, using arr[a][b].

#define size_1D 3
#define size_2D 3

void fun(int arr[][3])
{
   for(int i = 0; i < size_1D ; i++) {
    for(int j = 0; j < size_2D ; j++) {
        int value = arr[i][j];
    }
   }
}
int main()
{
    int arr[size_1D][size_2D] = {{1,2,7},{8,4,9}};
    fun(arr);
}

You can specify the sizes as arguments and use a variable length array declaration inside function parameter list. The compiler will do some job for you.

#include <stdlib.h>

void fun(size_t xmax, size_t ymax, int arr[xmax][ymax]);
// is equivalent to
void fun(size_t xmax, size_t ymax, int arr[][ymax]);
// is equivalent to
void fun(size_t xmax, size_t ymax, int (*arr)[ymax]);

void fun(size_t xmax, size_t ymax, int arr[xmax][ymax])
{
   for(int i = 0; i < xmax ; i++) {
    for(int j = 0; j < ymax ; j++) {
        int value = arr[i][j];
    }
   }
}
int main()
{
    int arr[3][4] = {{1,2,7},{8,4,9}};
    fun(3, 4, arr);
}

@edit

We know that the result of array subscript operator is exactly identical to pointer dereference operator of the sum:

a[b] <=> *(a + b)

From pointer arithmetic we know that:

type *pnt;
int a;
pnt + a = (typeof(pnt))(void*)((uintptr_t)(void*)pnt + a * sizeof(*pnt))
pnt + a = (int*)(void*)((uintptr_t)(void*)pnt + a * sizeof(type))

And that the array is equal to the value to the pointer to the first element of an array:

type pnt[A];
assert((uintptr_t)pnt == (uintptr_t)&pnt[0]);
assert((uintptr_t)pnt == (uintptr_t)&*(pnt + 0));
assert((uintptr_t)pnt == (uintptr_t)&*pnt);

So:

int arr[A][B];

then:

arr[x][y]

is equivalent to (ignore warnings, kind-of pseudocode):

*(*(arr + x) + y)
*( *(int[A][B])( (uintptr_t)arr + x * sizeof(int[B]) ) + y )
// ---- x * sizeof(int[B]) = x * B * sizeof(int)
*( *(int[A][B])( (uintptr_t)arr + x * B * sizeof(int) ) + y )
// ---- C11 6.5.2.1p3
*( (int[B])( (uintptr_t)arr + x * B * sizeof(int) ) + y )
*(int[B])( (uintptr_t)( (uintptr_t)arr + x * B * sizeof(int) ) + y * sizeof(int) )
// ---- *(int[B])( ... ) = (int)dereference( ... ) = *(int*)( ... )
// ---- loose braces - conversion from size_t to uintptr_t should be safe
*(int*)( (uintptr_t)arr + x * B * sizeof(int) + y * sizeof(int) )
*(int*)( (uintptr_t)arr + ( x * B  + y ) * sizeof(int) )
*(int*)( (uintptr_t)( &*arr ) + ( x * B  + y ) * sizeof(int) )
// ---- (uintptr_t)arr = (uintptr_t)&arr[0][0]
*(int*)( (uintptr_t)( &*(*(arr + 0) + 0) ) + ( x * B  + y ) * sizeof(int) )
*(int*)( (uintptr_t)( &arr[0][0] ) + ( x * B  + y ) * sizeof(int) )
*(int*)( (uintptr_t)&arr[0][0] + ( x * B  + y ) * sizeof(int) )
// ---- decayed typeof(&arr[0][0]) = int*
*( &arr[0][0] + ( x * B  + y ) )
(&arr[0][0])[x * B + y]

So:

arr[x][y] == (&arr[0][0])[x * B + y]
arr[x][y] == (&arr[0][0])[x * sizeof(*arr)/sizeof(**arr) + y]

On a sane architecture where sizeof(uintptr_t) == sizeof(size_t) == sizeof(int*) == sizeof(int**) and etc., and there is no difference in accessing data behind a int* pointer from accessing data behind int(*)[B] pointer etc. You should be safe with accessing one dimensional array when using a pointer to the first array member, as the operations should be equivalent ("safe" with exception for out-of-bound accesses, that's never safe)

Note, that this is correctly undefined behavior according to C standard and will not work on all architectures. Example: there could be an architecture, where data of the type int[A] are stored in different memory bank then int[A][B] data (by hardware, by design). So the type of the pointer tells the compiler which data bank to choose, so accessing the same data with the same to the value pointer, but with different pointer type, leads to UB, as the compiler chooses different data bank to access the data.

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

9 Comments

So you just ask is it safe to pass a pointer to first element of an N dimensional array and use it to iterate through it as a 1-dimensional array? It's safe.
@swaraj no it is not safe. The C language explicitly allows range checking. Passing the pointer to first innermost element allows you to only access the first row only. This has been explicitly listed in C11 J.2 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)."
@okovko there you're incorrect. For example strcpy is range-checking in recent gccs with known arrays.
This even though any bytes of an object can be accessed using a char pointer
array values are stored in contiguous memory locations well, yes (there is padding). The term "data bank" is something I used to describe the memory layout of an example esoteric architecture, it's not a term from C or anything, it doesn't mean anything. I just remember architecture which allowed some padding accesses only from specified memory region, so the compiler dependent on the type chose with memory region to use. So you could access int from one memory region, but couldn't from another. So casting one pointer to another type resulted in strange behaviors.
|

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.