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 int
s. The size of this first element is the size of an array of four int
s, which is just the size of four int
s.
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 int
s increments the pointer by the size of an array of four int
s.
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 int
s. 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.