0

In the following c/c++ code,

int main()
{
    int a[3] = {11, 22, 33};
    int *p[3];
    
    p[0] = &a[0];
    p[1] = &a[1];
    p[2] = &a[2];
    
    printf("(*p)[2] = %d\n",(*p)[2]);
    printf("*(p[2]) = %d\n",*(p[2]));
    return 0;
}

It turns out that (*p)[2] = *(p[2]) = a[2].

So, I understand that with int *p[3];, I am creating an array of three pointers, and with the subsequent three lines after int *p[3];, I am putting the address of a[0] into p[0], address of a[1] into p[1], and address of a[2] into p[2].

Then, with *(p[2]), I am retrieving the variable pointed by the address stored in p[2], which makes *(p[2]) = a[2]. I have two questions regarding this simple code:

  1. how to understand that (*p)[2] also equals a[2]?

  2. If p is no longer defined as an array of pointers to int variables, but an array of pointers to vectors of type int (std::vector in C++), does the equality (*p)[2] = *(p[2]) still hold?

user438383
  • 5,716
  • 8
  • 28
  • 43
Vic
  • 109
  • 2
  • 1
    C or C++? They are different languages with different rules. – user438383 Jun 07 '22 at 15:04
  • That is because you made your pointers point into the array that way. As an experiment try to assign `p[0]`..`p[2]` in reverse order and increase `a` by adding 2 more values. Check again... – Gerhardh Jun 07 '22 at 15:05
  • Welcome to Stack Overflow! To keep your question focused, please specify a language, C or C++. Lots of users get really touchy about that :) – MANA624 Jun 07 '22 at 15:05
  • now I would like to see an answer where it's explained how the answer would be different if this question was tagged c or c++ tho. – Federico Jun 07 '22 at 15:07
  • I think there's some UB going on, changing order of pointers starts to output a different value? https://godbolt.org/z/693sPsxY8 – Yksisarvinen Jun 07 '22 at 15:10
  • @user438383, I think for the first question, c and c++ are the same, but for the second question involving std::vector, it is c++. – Vic Jun 07 '22 at 15:15
  • @Yksisarvinen Yes, that´s true. So *(p[2]) should be the right way to access the data we wanted. The other way is simply not correct. – Vic Jun 07 '22 at 15:21
  • Your example is special case, since `p` matches exactly order of `a`, so as result you are reaching same destination using different rote. If you shuffle `p` then result is different and leads to Undefined Behavior https://godbolt.org/z/4jz18Tx1q here is version without UB: https://godbolt.org/z/P5M7Mbb6o – Marek R Jun 07 '22 at 15:44

2 Answers2

4

*p is the same as p[0], which is a pointer to a[0]. So (*p)[2] is a[2].

p[2] is a pointer to a[2], so *(a[2]) is also a[2].

Nothing magical going on here. It's just the way you have set up your pointers.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
2

It's because your array of pointers is an array of pointers that all point within the same array.

(*p)[2] effectively expands to (&a[0])[2] (because &a[0] is in p[0]), reading the second element of a by starting from &a[0] and skipping two elements.

*(p[2]) effectively expands to *(&a[2]) (because &a[2] is in p[2]), which is equivalent to (&a[2])[0], reading the second element of a by starting from &a[2] and not skipping any elements.

Either way, you end up reading the value from a[2], the only question is whether you're dereferencing a pointer that doesn't point to a[2] at an offset, or dereferencing a precalculated pointer to a[2] without an offset. You could get the same result with p[1][1] as well, it just doesn't look quite as symmetrical with the other two use cases.

Regardless, all of this works because the definition of p is weird; it's a toy to illustrate how pointers and indexing work.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • thanks so much for the explanation. So if we define "D2vector *p[3]" is an array of pointers pointing to vectors, where "D2vector" is the 2 dimensional vector we defined ourselves using std::vector, then *(p[0]) should be the right way to access the first vector, right? – Vic Jun 07 '22 at 15:33
  • Arguably not. If it's an array then use `[]` so everybody can see it's an array and if it's a pointer to a single object then use `*` so everybody can see that too. Even better don't use raw pointers and C arrays at all. Learn modern c++. – Goswin von Brederlow Jun 07 '22 at 16:54
  • @GoswinvonBrederlow: Vic is already using the operators you suggest; the `p[0]` is indexing `p` to get the first pointer, then `*(p[0])` dereferences it to find the `D2vector` that the pointer in `p[0]` points to. In practice, if you must write code like this, 95% of the time you'd actually do `p[0]->methodname()` which avoids the need for considering the precedence of `*` (because `->` is unambiguous and combines both the `*` and the `.` for method/attribute access). – ShadowRanger Jun 07 '22 at 17:29
  • @GoswinvonBrederlow: That said, I agree that there is basically zero reason to have such an array of pointers to C++ classes. I won't completely poo-poo the array (`std::array` adds some nice safety features, but it's cumbersome, so I'll admit to plain C array usage at times), but the pointers are likely unnecessary if `D2vector`: 1) Has a cheap enough default constructor or all elements can be constructed at array initialization time, and 2) Has an efficient move constructor/move assignment operator (e.g. via [copy-and-swap](https://stackoverflow.com/q/3279543/364696)). – ShadowRanger Jun 07 '22 at 17:32
  • If both cases apply, then you can move and swap array elements around cheaply enough, which is the main benefit an array of pointers gives you most of the time. If said cases don't apply, `std::unique_ptr` or `std::shared_ptr` probably applies; it's ugly, but it's better than unnecessary manual memory management. – ShadowRanger Jun 07 '22 at 17:34
  • @ShadowRanger I have no idea what Vic is doing because the example code is just a plain mess. It looks like a C style reference wrapper around elements of `a`. But in the comment it's supposed to be a `D2Vector`, so some kind of matrix. If you write code like this it's wrong 90% of the time and should be changed 100% of the time. There are many reasons why C++ has std::array and std::vector. – Goswin von Brederlow Jun 07 '22 at 17:36
  • @GoswinvonBrederlow One scenario I can think of is that the dimension of the vectors is not fixed yet, so we can only define the pointers to the vectors, and then use "new" to ask for the appropriate address when the dimension of the vector is known at later stage. If the dimensions of the vectors are known, then we can simply define the D2Vector without using pointers. – Vic Jun 07 '22 at 23:29
  • @ShadowRanger Let´s say: we have some data that is only 2 dimensional (stored in D2Vector), but we do not have any information on the size of D2Vector when we need to declare it. My understanding is that we resort to use a pointer even in modern C++, then use "new" to allocate memory of appropriate size when we know the size of the D2Vector. Does that make sense or is there a better way to handle this situation. – Vic Jun 07 '22 at 23:35
  • @Vic: The C++ solution to something like that would be to use a `std::vector`, optionally calling `.reserve(N)` on up front if you know exactly how many objects you'll eventually have. If you don't have an exact count, make sure you've got an efficient `noexcept` move constructor & assignment operator implemented (a good idea in any event; you can get both cheaply with [the copy-and-swap idiom](https://stackoverflow.com/q/3279543/364696)) so `vector` can efficiently handle resizing. Once that's done, just call `.emplace_back` on the `vector` when you know what needs to be appended. – ShadowRanger Jun 07 '22 at 23:39
  • @Vic: Ideally, `D2Vector` itself is following the Rule of Zero and storing its data in a `std::vector` itself, and you don't need to implement copy/move/destructors at all. – ShadowRanger Jun 07 '22 at 23:40
  • @Vic Just define a vector and then make Matrix a vector of vector. – Goswin von Brederlow Jun 08 '22 at 07:37