-1

Say I have the following array:

int array[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};

I know that I can twice dereference it to get any value I want from the array. For example:

printf("%d", *(*array+8));
//Output will be 9

It kind of made me assume that C doesn't really differentiate between one-dimensional and multidimensional arrays, and so a multidimensional array of dimensions 4x4 will be stored in memory in the same manner as a one-dimensional array with 16 elements. So I thought that if I wanted to use a function to do something only to the second half of the array, I could just pass it to the function like that:

void foo(array+8)
{
    
}

But it doesn't work and results in a segmentation fault.

So, IS there a way to pass to the function only the latter half of the array (in the aforementioned example, that would be {{9,10,11,12},{13,14,15,16}})? If such a way exists, I would be really happy to hear about it.

Thanks ahead :)

Blabla
  • 121
  • 5
  • 1
    `*(*array+8)` is undefined behaviour. The C standard explicitly states that you cannot use a multidimensional array as if it was a flat array. – n. m. could be an AI Jul 06 '23 at 08:08
  • Related: 1. [One-dimensional access to a multidimensional array: is it well-defined behaviour?](https://stackoverflow.com/q/6290956/12149471) 2. [Multidimensional array out of bound access](https://stackoverflow.com/q/48219108/12149471) – Andreas Wenzel Jul 06 '23 at 08:12
  • Even if the behavior is undefined according to the ISO C standard, in my experience, it will work on most platforms in most situations. If you want us to tell you what is going on, then please provide a [mre] of the code that is causing a segmentation fault. This code should include a function `main` and all `#include` directives. – Andreas Wenzel Jul 06 '23 at 08:18
  • Note that your `printf` has an asterisk `(*array+8)` that you forgot in the function call `(array+8)`. That makes a big difference. – user3386109 Jul 06 '23 at 08:31
  • `void foo(array+8)` hmmm... can you compile that code!? – Support Ukraine Jul 06 '23 at 08:56
  • Please clarify what `void foo(array+8)` is supposed to mean. Post the actual code that gives you the seg fault, not some pseudo code. – Lundin Jul 06 '23 at 09:01
  • Further, `array + 8` will not give what you seem to expect... you probably want `*array + 8` – Support Ukraine Jul 06 '23 at 09:01

3 Answers3

3

Arrays are genuine types in C and not pointers as is often carelessly suggested. Arrays decay to pointers to their first elements in most expressions, and this is why array + 8 is not working for you in a function call.

It is true that multidimensional arrays are stored in contiguous memory, but a 2d array, for example, is an array of 1d arrays. Consider the 2d array:

int array[4][4] = { { 1,  2,  3,  4  },
                    { 5,  6,  7,  8  },
                    { 9,  10, 11, 12 },
                    { 13, 14, 15, 16 } };

This is an array of four arrays of four int elements. Since arrays decay to pointers to their first elements, in a function call foo(array) the array identifier array decays to a pointer to its first element, which is an array of four ints. The size of this first element is the size of an array of four ints, which is just the size of four ints.

Pointer arithmetic is such that adding an integer to the value of a pointer increments the pointer value by the integral multiple of the size of the referenced type. That is, adding 1 to a pointer to int increments the pointer by the size of one int, and adding 1 to a pointer to an array of four ints increments the pointer by the size of an array of four ints.

So, the function call foo(array + 8) attempts to increment a pointer to the first array of array by the size of eight arrays of four ints. Now, array + 3 would result in a pointer to the fourth array of array, i.e., the last row of the 2d array, while array + 4 would result in a pointer "one past" the last element of array. Any attempt to form an address beyond one past the end of an array using pointer arithmetic results in undefined behavior in C, so the function call foo(array + 8) results in undefined behavior regardless of anything that happens in foo.

With this in mind, you can pass the latter half of the array by passing the pointer formed by array + 2:

#include <stdio.h>

void array_print_2d(size_t rows, size_t cols, int arr[][cols]) {
    for (size_t i = 0; i < rows; i++) {
        for (size_t j = 0; j < cols; j++) {
            printf("%-2d ", arr[i][j]);
        }
        putchar('\n');
    }
}

int main(void) {
    int array[4][4] = { { 1,  2,  3,  4  },
                        { 5,  6,  7,  8  },
                        { 9,  10, 11, 12 },
                        { 13, 14, 15, 16 } };
    array_print_2d(2, 4, array + 2);
}

Example run:

$ ./array_half 
9  10 11 12 
13 14 15 16 

Here array_print_2d takes a variable length array (added in C99, optional in later standards but widely available) and the number of rows and columns of the array. By passing array + 2 as the array argument, a pointer to the third row of the 2d array is passed. Note that this means that the array "seen" by the function has only two rows, and this must be accounted for in the function call.

Given the discussion above about pointer arithmetic, it might seem that you could do something like:

int *elem = &array[0][0];
foo(elem + 8);

Here elem is a pointer to the first element of the first row of array, and incrementing that pointer increments by multiples of the size of an int. You can do this so long as the result of the operation does not point beyond one past the end of the first row, and so long as you don't attempt to dereference a pointer one past the end of the first row. But elem + 8 attempts to form an address well beyond one past the end of the first row, so this attempt to treat array as a flat 1d array leads to undefined behavior. While some implementations behave as you might expect here and some programmers take advantage of this, the C Standard is very clear that this is undefined behavior. Annex J of the C Standard shows a relevant example causing undefined behavior:

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).

The above citation is from the C99 Standard, but the example remains today in the C23 Standard.

There is some discussion of undefined behavior due to treatment of multidimensional arrays as flat arrays here.

ad absurdum
  • 19,498
  • 5
  • 37
  • 60
0

You can do:

void foo(int arr[][4], size_t len) // or: void foo(int (*arr)[4], size_t len)
{
}

(I added len to indicate how many elements of type int[4] the function should operate on.)

To pass the second half of the int array[4][4] to the function:

    foo(array + 2, 4 - 2);  // or: foo(&array[2], 4 - 2);

Regarding your question about flattening the entire array to a single dimensional array of int, e.g. by defining:

void bar(int arr[], size_t len) // or: void bar(int *arr, size_t len)
{
}

and calling it with the second half of array as:

    bar(&array[2][0], 8);

If function bar() accesses arr[4], that will result in undefined behavior according to the C standard because arr[4] is out of bounds of the one of the inner-dimensional array elements. It is effectively the same as if the caller accessed array[2][4]. Although several implementations will effectively treat array[2][4] as accessing the same element as array[3][0], it is technically undefined behavior.

Ian Abbott
  • 15,083
  • 19
  • 33
0

well, let's evaluate the expression you post, given the definition:

int array[4][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12},
    {13, 14, 15, 16},
};

The expression to interpret is:

*(*array + 8)

array is an array of four arrays of four elements, so its name appearing in an expression can be interpreted as the address of the first element of the array, so let's substitute it where array itself appears:

*(*&array[0] + 8)

but array[0] is, again, itself an array of 4 integers, and its address --as denoted by the & operator--, is a pointer to the array, this is an array of four integers, which dereferenced by the * operator, again gives the array of four integers at first position of array(see 1. below):

*(array[0] + 8)

now we have the first array which, appearing in an expression is the address of its first element, so the expression becomes:

*(&array[0][0] + 8)

and that is a pointer to int which, plus 8, gives you access to the ninth element of the array viewed as a linear array of elements (some will argue U.B. is invoked here, and strictly that is true, as we got out of bounds of the array) What is happening here is that, as all the elements of the two dimensional array are in sequence, the ninth element happens to be the element in position of array[2][0], which is 9. This is the output observed in the printout.

This can be tested by changing the initial value of that element only, and see that the printed result varies accordingly. But I insist, you have dereferenced the ninth element of an array that has only 4 elements (so the operation is not legal as a C construct). (this can be predicted only based upon the fact that the four integer arrays are composed themselves as array of arrays)

So, IS there a way to pass to the function only the latter half of the array (in the aforementioned example, that would be {{9,10,11,12},{13,14,15,16}})? If such a way exists, I would be really happy to hear about it.

Yes, the array is linear, so array + 2 will be a pointer to the third element (this is a pointer to a four element array) two places higher (this is two times the size of a cell, which is the size of a full row of the matrix)

We can also interpret this expression as an exercise:

array + 2

array will be interpreted as a pointer to the first element (this is a pointer to the first array of four ints),

&array[0] + 2

which is the address of an array of four integers, shifted up two positions, this is two rows of the array...

... plus 2 which is two arrays of four ints further, so it will point to the third row of the array, and will have type int (*)[4] (which is compatible with the parameter definition below). So the way of doing what you want is to call foo() as:

   foo(array + 2);

and this is perfectly legal. You should define foo() with the following prototype:

void foo(int (*array)[4]);

This is the function will take a pointer to .

Notes:

  1. There's generally a subtle misunderstanding here, because the array name (this is array) by itself means one thing while the array address (this is &array) means a different thing. The array name itself in C means the address of it's first element (which is a special interpretation that arrays have), so its type is a pointer to the array cell type, while the adress of the array means a pointer to the array itself, so it's of type pointer to an array of 4 integers, and not a pointer to int. This allows the * and & operators to cancel each other, and is a consequence of the evaluation of the [0] index that happens before the interpretation of & operator ---as it has higher precedence than &)
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31