0

I am trying to print a 2-D array in C by using pointers but I am not getting the expected output.

Program:-

#include <stdio.h>
int main()
{
    int arr[2][3] = {{1,2,3},{4,5,6}};
    int* p;
    for ( p = arr; p <= arr+6; p++)
    {
        printf("%d ", *p);
    }
    return 0;
}

Output:-

1 2 3 4 5 6 -1116112128 1587637938 0 0 1893963109 32521 -1453950296 32766 -1453805568 1 800797033 21984 -1453949463

Could you tell me where I am wrong as the output should only be:

1 2 3 4 5 6

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    If you remember that for any pointer or array `arr` and index `i`, the expression `arr[i]` is exactly the same as `*(arr + i)`. Now if we substitute `6` for the index `i` then we get `*(arr + 6)` which is the same as `arr[6]`. Which is way out of bounds of your two-element array `arr`. – Some programmer dude Feb 10 '22 at 15:48
  • 1
    Also, `p = arr` is wrong. As arrays decays to pointers to their first element, `p = arr` is the same as `p = &arr[0]`. And `&arr[0]` is the pointer to the first sub-array, and will have the type `int(*)[3]`. Which is very different from the type `int*` that you declares `p` to be. – Some programmer dude Feb 10 '22 at 15:50
  • 1
    Use `int *p = &arr[0][0];` — and test `p <= &arr[1][2]` for this array. If your array is defined with `int arr[ARR_ROWS][ARR_COLS];` then test `p <= &arr[ARR_ROWS-1][ARR_COLS-1]`. – Jonathan Leffler Feb 10 '22 at 17:07

5 Answers5

1

Could you tell me where I am wrong

The elements of arr are not integers, but arrays of 3 integers. So arr+6 is surely a different address than what you expect, since pointer arithmetic works in multiples of the size of the type in the array.

You'll always be better off using nested loops to iterate over a multidimensional array; treating it as one single-dimensional array of int leads to exactly the kinds of confusion you see here. The code is harder to understand and verify, it won't be any slower.

Caleb
  • 124,013
  • 19
  • 183
  • 272
0

Array designators used in expressions with rare exceptions are implicitly converted to pointers to their first elements.

The type of the array elements of this array

int arr[2][3];

is int [3]. So a pointer to the first element of the array has the type int ( * )[3].

This assignment

p = arr;

where p has the type int * is incorrect because the operands of the assignment have incompatible pointer types.

At least you need to cast the right expression to the type int * like

p = ( int * )arr;

The same casting you need to use in the condition in the for loop. That is instead of

p <= arr+6

you have to write

p < ( int * )arr+6

Below there is a demonstration program that shows how to output a two-dimensional array as a two-dimensional array using pointers.

#include <stdio.h>

int main( void )
{
    int arr[2][3] = {{1,2,3},{4,5,6}};

    for ( int ( *p )[3] = arr; p != arr + 2; p++ )
    {
        for ( int *q = *p; q != *p + 3; ++q )
        {
            printf( "%d ", *q );
        }
        putchar( '\n' );
    }

    return 0;
}

If you want to output the two-dimensional array as a one-dimensional array then you can write

#include <stdio.h>

int main( void )
{
    int arr[2][3] = {{1,2,3},{4,5,6}};

    for ( int *p = ( int * )arr; p != ( int * )arr + 6; p++ )
    {
        printf( "%d ", *p );
    }
    putchar( '\n' );

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • does the second approach invoke UB? – tstanisl Feb 10 '22 at 17:13
  • @tstanisl No there is neither undefined behavior. – Vlad from Moscow Feb 10 '22 at 17:18
  • I mean that initial p is pointing to `arr[0][0]` which is a part of `int[3]` array. Now the question is if `p + 3` is valid for access – tstanisl Feb 10 '22 at 17:21
  • @tstanisl It is a valid access. The memory is interpreted as a one dimensional array the same way when you use malloc to allocate an array. – Vlad from Moscow Feb 10 '22 at 17:23
  • I think that you should use `char*` to do the pointer arithmetic. AFAIK, `int[2][3]` cannot be aliased with `int[6]`, but it can be aliased with `char[sizeof(int[2][3])]`. – tstanisl Feb 10 '22 at 17:29
  • @tstanisl There is neither aliasing. You initially has the pointer of the type int * and work with it. – Vlad from Moscow Feb 10 '22 at 17:32
  • you initially have a pointer to `int[3]`, cast to `int*`, so it points to the first element of **3** element array of `int` – tstanisl Feb 10 '22 at 17:34
  • @tstanisl The pointer points to the initial object of the type int of the two-dimensional array. That is the pointer points to an extent of memory occupied by objects of the type int without any gaps. That is the increased point points to a valid object. For example you may write int *p = calloc( 2, sizeof( int ) ); and you may write int ( *p )[2] = calloc( 2, sizeof( int ) ); – Vlad from Moscow Feb 10 '22 at 17:36
  • I mean the C standard explicitly forbids pointer arithmetic that cross the arrays boundary. So the transition from `(int*)arr + 3` to `(int*)arr + 4` may be undefined to my understanding of standard. Similar to accessing `s.a[3]` to modify `s.b[0]` in `struct { int a[3], b[3]; } s;` (assume no padding). I know about guarantees for layout of 2d arrays but it is important only when accessing as via `char` or `memcpy`. – tstanisl Feb 10 '22 at 17:42
  • @tstanisl And what about qsort or bsearch? Do they have undefined behavior?:) – Vlad from Moscow Feb 10 '22 at 17:44
  • the effective type of aggregate type of dynamic objects is a mess. I've asked a question about it some time ago. See https://stackoverflow.com/q/70185038/4989451. However in this case the *automatic* object is used. So the type is fully defined and immutable. – tstanisl Feb 10 '22 at 17:46
  • `qsort` and `bsearch` are standard library function. So the limitation of standard do not apply. – tstanisl Feb 10 '22 at 17:46
  • @tstanisl I have understood nothing. If qsort and bsearch are standard functions then it means that they can break the standard?:) When you use the expression a[i][j] then the sub-expression a[i] has the type int *. In fact you deal with the pointer of the type int *. – Vlad from Moscow Feb 10 '22 at 17:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/241914/discussion-between-tstanisl-and-vlad-from-moscow). – tstanisl Feb 10 '22 at 17:49
  • @tstanisl, `p` is pointing to `arr[0][0]` but `p` is not of type `int (*)[3]`, but just `int *`. This is why you increment `p` and get the next int, but if you add 1 to `arr` you add one full row, and if you add one to `&arr` you add one full 2x3 2D array. Read my answer for better explanation. – Luis Colorado Feb 10 '22 at 22:35
  • @tstanisl, the standard allows a pointer to point one position past the end of the array (but pointing outside) Derreferencing the pointer is what is not allowed in this case, but for making comparisons it's perfectly valid in the C standard. – Luis Colorado Feb 10 '22 at 22:38
  • @LuisColorado, it's more complex and subtle issue. Basically `(int*)arr ` is equivalent to `&arr[0][0]`. Therefore `(int*)arr + 3` is equivalent to `&arr[0][3]`, not `&arr[1][0]`. And accessing `int` object through such an expression is undefined because it overflows 3-integer-long array `arr[0]`. Therefore I think UB is invoked here. – tstanisl Feb 10 '22 at 22:53
0

First, when looping through arrays of size n wth an index i, the condition for continuation should be i < n rather than i <= n, because array indexes in C run from 0 through n-1.

However, your code has a more serious error: 1-dimensional arrays can be 'decayed' into pointers to the elements' type; however, 2-dimensional arrays decay into pointers to 1-dimensional arrays. So, in your case, the type of the pointer used in the arr + 6 expression is a pointer to an array of three integers; further, when the 6 is added, that operation is performed in terms of the size of the pointed-to object, which is sizeof(int) * 3 – so, even when changing the <= to <, you will be running far beyond the actual bounds of the array.

To make the pointer arithmetic work in the correct 'units' (i.e. sizeof(int)), cast the arr to an int* before the addition (and also change the <= to <):

#include <stdio.h>
int main()
{
    int arr[2][3] = { {1,2,3},{4,5,6} };
    int* p;
    for (p = (int*)arr; p < (int*)arr + 6; p++) {
        printf("%d ", *p);
    }
    return 0;
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • arrays that are passed as parameters, and declared as arrays in the parameter declaration is what decays to pointers.... and only to row pointers in this case (this is as `int (*)[3]`, but never an array declared as an automatic variable here. Please don't contribute this way, you have a lot of reputation to say those things. – Luis Colorado Feb 10 '22 at 22:50
  • @LuisColorado Passing as a function parameter is *not* the only way (named) arrays can decay: [What is array to pointer decay?](https://stackoverflow.com/q/1461432/10871073) – Adrian Mole Feb 10 '22 at 22:54
0

You are trying to access the value in the wrong way, The two-dimensional array is saved as a continuous block in the memory. So, if we increment the value of ptr by 1 we will move to the next block in the allocated memory.

int arr[2][3] = {{1,2,3},{4,5,6}};
int *ptr = arr;

int i,j;
for (i = 0; i < 6; i++) {
    printf("%d ", *(ptr + i));
}
return 0;
Syed M Sohaib
  • 307
  • 1
  • 8
0

In

    for ( p = arr; p <= arr+6; p++)

the expression arr, as an rvalue, is a pointer to the first element of the array (which is of type int [3], so each time you increment that pointer, it moves three int positions forward ---a whole row---, and so, arr + 6 points just after the sixth row of the array (if the array should ever had six rows) You can do it (with the proper explicit pointer conversions, as you are mixing pointers to int with pointers to int [3]) with the expression arr + 2 which is the addres of the first array element after the second row (and the number of rows of the array).

You can do it also declaring

    int (*aux)[2][3] = &arr;  /* aux is a pointer to the whole 3x2 array,
                        * so aux + 1 will be the position of the second
                        * 2D array after this one */

and then

    int *end = (int *)(aux + 1);

or simply

    int *end = (int *)(&arr + 1); /* see below */

(Beware that arr and &arr are both pointers and point to the same place, but they are not the same type (arr is of type int (*)[3] and &arr is of type int(*)[2][3])

So let's rewrite your code as

    for (p = (int *)arr; p < end; p++)

or

    for (p = (int *)arr; p < (int *)&arr + 1; p++)

would work, which seems more natural to do the calculus in complete array units than in rows or single cells (and you can change freely the dimensions of the array)

Your code would be:

#include <stdio.h>
int main()
{
    int arr[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
    int *end = (int *)(&arr + 1); /* try to avoid evaluating this expression in the loop 
                                   * despite that it can be optimized to comparing
                                   * with a constant value */
    char *sep = "";
    for (int *p = (int *)arr; p < end; p++)
    {
        printf("%s%d", sep, *p);
        sep = ", ";
    }
    putchar('\n');
    return 0;
}

(Beware that you have to use < operator and not <= as you don't want to print the value pointed by end, because it lies one place outside of the array)

Finally a note: this will work with true arrays, but not with function parameters declared as arrays, because they decay to pointers and then &arr is not a pointer to data the size of the array, but it is the address of the parameter itself, which points to the array somewhere else.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31