0

im writting a program in which i want to use this piece of code to transfer a 2d array in a function, but i dont understand fully how it works exactly. Can someone explain it, specifically line 7?


#include <stdio.h>
void print(int *arr, int m, int n)
{
    int i, j;
    for (i = 0; i < m; i++)
      for (j = 0; j < n; j++)
        printf("%d ", *((arr+i*n) + j));
}
 
int main()
{
    int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int m = 3, n = 3;
 
    // We can also use "print(&arr[0][0], m, n);"
    print((int *)arr, m, n);
    return 0;
}

i also tried using

*( *(p + i) + j)

instead, but it didnt really work and i dont know why so if someone can explain why this didnt work as well i would really appreciate it.

Fe2O3
  • 6,077
  • 2
  • 4
  • 20
Draisen
  • 1
  • 1
  • Grab a pen and write down the values of `i`, `j` and `n` AND each of the intermediate values calculated for each of the 3x3 loops of the print statement in the function. That's a start... When you see what is happening with those numbers, you may come to understand what the statement is doing. (PS: C guarantees that all elements of an array are _contiguous_ regardless if the array is 1D or 2D (or 3D...)...) Clue: a 3x3 array is stored just like a 1x9 array... – Fe2O3 Jan 01 '23 at 22:26
  • In `print`, assuming `m`is the height and `n` is the width, try: `arr[(i * n) + j]` where `i` is the row index and `j` is the column index – Craig Estey Jan 01 '23 at 22:52
  • Tip: Code like this is more clearly explained when `m != n`. Use different values. – chux - Reinstate Monica Jan 02 '23 at 01:17
  • @Draisen, Cast not needed and best avoided. Could use `print(arr[0], m, n);`. – chux - Reinstate Monica Jan 02 '23 at 01:25

4 Answers4

1

In modern C, you should use Variable Length Array types introduced in C99.

void print(int m, int n, int arr[m][n])
{
    int i, j;
    for (i = 0; i < m; i++)
      for (j = 0; j < n; j++)
        printf("%d ", arr[i][j]);
}

The function should be called with a simple:

int main()
{
    int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int m = 3, n = 3;
 
    print(m, n, arr);
    return 0;
}

The VLA types are optional feature in C11 but they will be mandatory again in C23.

tstanisl
  • 13,520
  • 2
  • 25
  • 40
0

You pass a 2-dimesional Array to your print function, with the amount of items in the individual arrays and the amount of arrays in the 2D-Array.

Now let us come to the loop:

First of all if i and j are both zero you get the first Item of the first Arrays. In the next Iteration of the inner loop j is 1, thus (arr+i*n) + 1 points to 2 Element of the first Arrays, because i is still zero and j will be 1 ((arr + 0 * 3) + 1). In the next iteration it is the same but i is 2, thus pointing to the second element.

When the inner loop has finished i is increased to 1 and the expression is now (arr + 1 * 3) + 0. So now i * 3 will point to the first element of the second Array.

And in the third iteration of the outer loop i will point to the first element of the third array. So i * 3 is always the pointer to the first element of an array, in this 2D-Array and the + j always points to an individual element in the Array. By combining this the 2D-Array gets printed.

*( *(p + i) + j) Does not work because, assuming p is an pointer to the array arr, because you are dereferencing it, so it in the first iteration it would evaluate to *(1 + 0) which results in a segmentation fault because you are not allowed to read this Memory Adress. This is, because by dereferencing it you are *(p + 0) referring to the first Element of the first Array, which is 1.

Bentus
  • 1
  • 2
0

In int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};, 1, 2, and 3 initialize an array of 3 int. An array is a contiguously allocated set of objects, so 1, 2, and 3 are contiguous in memory. 4, 5, and 6 initialize another array of 3 int, and so do 7, 8, and 9. These three arrays of 3 int are themselves another array, an array of 3 arrays of 3 int. Since an array is a contiguously allocated set of objects, the 3 arrays are contiguous in memory. So the 4, 5, and 6 follow the 1, 2, and 3, and the 7, 8, and 9 followed the 4, 5, and 6.

So the overall effect is that 1, 2, 3, 4, 5, 6, 7, 8, and 9 are contiguous and consecutive in memory.

*((arr+i*n) + j) uses this fact to calculate the location of the element in row i and column j. The starting address of the array is arr. i*n is the number of elements from the start to row i. That is, each row has n elements, so i rows have i*n elements. Then j is the number of elements from the start of the row to the element in column j of that row. So arr + i*n + j is where the element in row i, column j is, and *(arr + i*n + j) is that element. The extra parentheses in *((arr+i*n) + j) are unnecessary.

This code abuses the C type model. In main, arr is an array of 3 arrays of 3 int. When main calls print, it passes (int *)arr. This passes a pointer to an int instead of a pointer to an array of 3 int, and then print bypasses the normal application of array types to accessing the memory. Technically, the behavior of this code is not defined by the C standard, but it works in many C implementations.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
-1

C is an extremely simple language, it became popular mainly because the simple parts were designed to be combined in ways that replaced complex parts of previous languages (see for as an example). One side effect is that it leaves out parts you expect in other languages.

Specifically for arrays, an array has no information on its size or format, it's assumed that the programmer will keep track of that, or that the size of every dimension but the first is constant (and normally the first one as well). So however many dimensions it's declared as, an array is just a single block of memory large enough to hold all elements, and the location is calculated internally using the [] operator.

Fun fact, C allows you to specify a[1] as 1[a], because it all translates to addition and multiplication. But don't do that.

In the event that you have an array that has variable sizes for more than one dimension, C doesn't support that so you have to do the math yourself, which is what that print() function is doing, where m and n are the sizes of the dimensions. The first row starts at arr (or arr + 0), and goes to arr + (n - 1) (0 to n-1 is n elements), and would look like arr[0][0] to arr[0][n-1] in a language that supported it. The next row starts at arr + n (would be arr[1][0]) to arr + (2 * n) - 1, and so on (up to what would be arr[m-1][n-1]).

In the function here, i and j go from 0 to m-1 and n-1 respectively, so you don't see - 1 in the code.

One more thing, C is at least helpful enough to know when you use + on a pointer, you mean to increment by the size of the thing you're pointing to, so you don't have to figure out how many bytes in a int.

John Bayko
  • 746
  • 4
  • 7
  • "an array has no information on its size or format" is amiss. The size of an array is `sizeof arr`. For `arr`, it is a `sizeof arr / sizeof arr[0]` by `sizeof arr[0] / sizeof arr[0][0]` 2D array. – chux - Reinstate Monica Jan 02 '23 at 01:21
  • That's information that the compiler has, but it's not stored with the actual array, so is lost by the time the code runs. The array as stored in memory is just a sequence of elements with no structure or indication of size. – John Bayko Jan 02 '23 at 04:49
  • Re “the size of every dimension but the first is constant (and normally the first one as well)”: The C standard allows variable length arrays in all dimensions. – Eric Postpischil Jan 02 '23 at 12:34
  • Better phrasing might be "fixed at the point of declaration". Didn't know how much technical detail the original poster would get or want, but you're right, I shouldn't be inaccurate. – John Bayko Jan 02 '23 at 17:30