0

I can't figure out why output of this code is 8. I have deduced that *b + x equals to first element of array a + x*4, but I don't know why. Can someone please elaborate?

#include <iostream>

using namespace std;

int main()
{
    int a[2][3] = { {4,3, 7},{2,6, 9} };
    int** b = (int**)a;
    cout << *b + 1 << endl;
}
drescherjm
  • 10,365
  • 5
  • 44
  • 64
  • 2
    What do you think the result should be instead? Why? – Karl Knechtel Sep 16 '21 at 22:54
  • 5
    Also: when you write `int** b`, what do you think that means about the type? In particular, how many times do you think you should have to apply the `*` operator to that value, in order to find an `int`? Why? When you write `(int**)a`, why do you expect this cast to make sense? When you declare `int a[2][3] = { {4,3, 7},{2,6, 9} }`, what do you think that actually implies about the structure of data in memory? Do you think this causes any pointers to be written into memory? Where? Why? – Karl Knechtel Sep 16 '21 at 22:56
  • 2
    Does https://stackoverflow.com/questions/8617466/a-pointer-to-2d-array help? How about http://c-faq.com/aryptr/ ? – Karl Knechtel Sep 16 '21 at 22:58
  • 1
    `(int**)` is an [Explicit Conversion](https://en.cppreference.com/w/cpp/language/explicit_cast). I call it a God Cast because the compiler will treat it like the word of God. It will treat the variable as if it is the named type no matter how ill-advised the conversion is. When you see one of these in code, approach the code from the assumption that it's a bug and you'll seldom be wrong. – user4581301 Sep 16 '21 at 23:15
  • An `int[2][3]` does not decay into an `int**`, so the statement `int** b = (int**)a;` is creating **undefined behavior** for the subsequent `*b` statement. – Remy Lebeau Sep 16 '21 at 23:34

2 Answers2

0

Here's a more illustrative example:

#include <iostream>

using namespace std;

int main() {
    int a[2][3] = { {4, 3, 7}, {2, 6, 9} };
    int * b = (int *)a + 4;
    cout << *b << endl; // output is 6

    int c[2 * 3] = { 4, 3, 7, 2, 6, 9 };
    int * d = c + 4;
    cout << *d << endl; // output is 6
}

b and d are both pointers to ints. They are pointing to the base of their respective arrays plus four. With both double- and single-dimensional arrays, this is the memory corresponding to '6'.

It's that C/C++ is doing the math of 2 * 3 behind your back, and allocating space on the stack of 6 * sizeof(int) (ignoring padding). So the pointer arithmetic works out the same.

I'm guessing you ran in to trouble with something like:

        int * b = a + 4;

star.cpp:7:15: error: cannot initialize a variable of type 'int *' with an rvalue of type 'int (*)[3]'
        int * b = a + 4;
              ^   ~~~~~
1 error generated.

Which led you try the int ** b. This isn't a pointer to an int in a double-dimensional array, it's a pointer to a pointer to an int.

Not really sure what the goal is, but a much simpler implementation would be:

    #include <iostream>

    using namespace std;

    int main() {
        int a[2][3] = { {4, 3, 7}, {2, 6, 9} };
        int b = a[1][1];
        cout << b << endl; // output is 6
    }
Scott S
  • 341
  • 4
  • 11
-2

C++ and C views of arrays are not intuitive. In particular:

int a1[8];
int a2[4][2];
int a3[2][2][2];

These are all identical, except for how you access the memory. In addition, these assignments are all valid (though will probably generate warnings, because they're usually wrong):

int *b1 = a1;
int *b2 = a2;
int *b3 = a3;

And in particular, b3[7] is the same as a3[1][1][1].

Oh, also, C++ and C don't care what index you use, so a1[1000000] is allowed without question, though the underlying OS will probably not tolerate it and kill your program - unless it doesn't and you get a bug or security flaw.

This is different from, eg. Java or C#, where int a[2][2]; actually allocates one array of references to two arrays of integers, which is probably what you're thinking of.

Avoid arrays if you can, use safer constructs like vectors.

John Bayko
  • 746
  • 4
  • 7
  • *These are all identical* No they have different types. *will probably generate warnings, because they're usually wrong* Two of them are flat out wrong and will generate compilation-halting errors in C++: https://godbolt.org/z/a79W5fTbz – user4581301 Sep 17 '21 at 00:03
  • *so `a1[1000000]` is allowed without question* Not allowed in either language. Unfortunately there are generally no checks to prevent it. The assumption is that the programmer's not a dumb and knows what they are doing. Some compilers are kinder than others, though: https://godbolt.org/z/fx956zxPr – user4581301 Sep 17 '21 at 00:05
  • Oops, I named my test file with `.c` instead of `.cpp` and it compiled with warnings, but ran. It doesn't when compiled as C++. – John Bayko Sep 17 '21 at 17:18
  • Accessing `a1[1000000]` does generate a warning in C++ (because the index is a constant), but still runs (with a segmentation fault, from the OS, and not the language), so that's valid, and I stand by the advice to avoid arrays in C++ when possible, since there are safer alternatives. – John Bayko Sep 17 '21 at 17:24
  • Agreed, with caveat: The `[]` operators in `std::array` and `std::vector` also do not require any bounds checking. If you are willing to pay the costs of bounds checking, a wonderful thing to do while debugging, use the `at` method. – user4581301 Sep 17 '21 at 17:36