6

In C/C++ we know pointers. That is, a variable memory address.

say, this code bellow will not compile, but the idea is this:

#include <iostream>
using namespace std;

main() {
    int* pi;
    int i = 1;
    pi = &i;
    
    while (true) {
        cout<<"i = " << i++ << "; pi = " << pi;
        pi = &pi;
    }
}

how far can we go with the address of the address. Where is the "final address"?

int* pi = &i;
int** ppi = &pi;
int*** pppi = &ppi;
int**** ppppi = &pppi;
.....
serge
  • 13,940
  • 35
  • 121
  • 205
  • 1
    Pointers are just numeric values. They will be stored in a variable. So it's the same as asking "how many variables can I have" which I suppose you know the answer to. And if you just keep taking the pointer of the same variable, it's an infinite loop of a never-changing pointer value. – tenfour Oct 12 '20 at 09:10
  • 2
    There is no such thing as "the address of the address". There is a such thing as "the address of the variable". – Kit. Oct 12 '20 at 09:15
  • The guy who knows the guy who knows the guy... No limit except a syntactic one (can't remember exactly but `gcc` did have one ~256 levels of indirections). – Jean-Baptiste Yunès Oct 12 '20 at 09:23

6 Answers6

3

There's no theoretical limit, but bear in mind:

  1. You cannot legally use pi = &pi;, since that's a strict aliasing violation. The behaviour on reading pi subsequent to that is undefined.

  2. The behaviour on reading or dereferencing a pointer that is not initialised is undefined.

  3. The behaviour on reading a pointer that used to point to valid memory but no longer does is indeterminate. But it is not undefined. So the fact that your pseudo code will produce an infinite number of dangling pointers is moot.

You could engineer a working example with templates by the way (which keeps all the pointers in scope), much in the way as you can implement a factorial function using templates.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • My question is rather to see, what happens with the address of address, where it will go, and when it will be no more addresses to address to... – serge Oct 12 '20 at 09:06
  • @Serge: The problem is then you're getting too specific and the answer has to become "the behaviour of your code is undefined". – Bathsheba Oct 12 '20 at 09:09
  • If you have a void pointer and make it point to itself: `void *p; p = &p;`, is that a strict aliasing violation? – printf Oct 12 '20 at 09:32
  • 1
    @printf pointing to something is never an aliasing violation by itself. Indirecting through the pointer of incompatible type and producing an rvalue would be a violation. You cannot indirect through a `void*`. – eerorika Oct 12 '20 at 10:10
3

For fun, here's an example where you can generate long pointer types using templates. The compiler will generate types with hundreds of levels of indirection.

#include <iostream>

template<size_t N, typename T>
struct PointerVariable
{
    T* myPtr;
    PointerVariable<N - 1, T*> ptrToMyPtr;

    PointerVariable(T* p) :
        myPtr(p),
        ptrToMyPtr(&myPtr)
    {
        // display pointer value
        std::cout << "ptr:" << ((void*)myPtr) << std::endl;
    }
};

template<typename T>
struct PointerVariable<0, T>
{
    PointerVariable(T* p) {
        int a = 3.3; // generate a compiler warning so we can see the names of types
    }
};

int main()
{
    int val = 0;
    auto pp = PointerVariable<499, int>{ &val };
    return 0;
}

On MSVC++2019, this produces a compiler warning:

warning C4244: 'initializing': conversion from 'double' to 'int', possible loss of data
message : while compiling class template member function 'PointerVariable<0,T *>::PointerVariable(int ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************)'

And when N=500, a compiler error occurs:

fatal error C1202: recursive type or function dependency context too complex

Using an explicit type, VC++2019 allows 1491 levels of indirection:

int
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128

    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    ******************************************************************************************************************************** // 128
    **************************************************************** // 64
    **************** // 16
    *** // 3
    x = nullptr;

Adding one more pointer level emits:

fatal error C1026: parser stack overflow, program too complex

This is an exercise of the compiler's abilities regarding variable storage and type complexity. Ignoring these 2 limitations, for example by just working with uintptr_t instead of real pointer types, then your limitation is system memory.

tenfour
  • 36,141
  • 15
  • 83
  • 142
2

In this construction, there is no theoretical limit, since you take addresses of different variables:

int* pi = &i;
int** ppi = &pi;
int*** pppi = &ppi;
int**** ppppi = &pppi;
.....

You have a variable i. You have a variable pi that contains the address of i. You have a variable ppi that contains the address of the variable pi. You have a variable pppi that contains the address of the variable ppi. And so on. This can go indefinitely, although of course this is never needed in practice.

printf
  • 325
  • 1
  • 7
  • is nout about practice or solving a concrete problem... is about understanding the memory structure... when we declare a "int i = 0", how many adresses we occupy in the memory? beause is not only "i" that is created. But also its address. And the address of its address. And so on... – serge Oct 12 '20 at 21:26
0

i, pi, ppi, pppi, … are all variables and for each of them, you can retrieve its address using &i, &pi, &ppi, &pppi, … it does not matter of which type these variables are.

int, int*, int**, int*** … are all just types, (the * belongs to the type).

You could type alias all of these:

using int_ptr_t = int*;
using int_ptr_ptr_t = int**;
using int_ptr_ptr_ptr_t = int***;

and write it that way:

int_ptr_t pi = &i;
int_ptr_ptr_t ppi = &pi;
int_ptr_ptr_ptr_t pppi = &ppi;

which would be equivalent to your:

int* pi = &i;
int** ppi = &pi;
int*** pppi = &ppi;

So it technically is only limited to the memory of the system.

t.niese
  • 39,256
  • 9
  • 74
  • 101
0

As @Bathsheba explained, there are several technical problems with your code. But if we ignore those, then I am afraid that your loop still does not make sense - at least not how I understand your intention.

main() {
    int* pi;
    int i = 1;
    pi = &i;   // <- **1**
    
    while (true) {
        cout<<"i = " << i++ << "; pi = " << pi;
        pi = &pi;    // <- **2**
    }
}

At 1 you conceptually write the address of i on the sheet of paper named pi. Then at 2 you then write the address of pi on pi. So far, if we just think of memory as sheets of paper on a desk, this does make sense.

However, the act of writing something on the pi sheet does not make it move to a new location. Once you have written the location of pi onto the pi sheet every further pi = &pi assignment changes nothing.

You can, of course, grab a stack of new papers, write the location of pi on the first, then write the location of that paper onto the next, and so on until you run out of paper. But that will just get you a stack of papers that all point at the last, and is not what I think you want.

One way to point out the flaw with your question is that while the pi sheet has a location on your desk, and while you can write the location down on any sheet. The location of a sheet is a concept intrinsic to that sheet, but the location of a sheet does not have a location of its own. This means that asking for the location of the location does not really make sense.

As @Kit wrote in his comment:

There is no such thing as "the address of the address". There is a such thing as "the address of the variable"

Frodyne
  • 3,547
  • 6
  • 16
  • I am afraid Kit is wrong. There is such a thing as "the address of the address", because the address of "i" is not a variable. – serge Oct 12 '20 at 21:28
  • @Serge What are you talking about? Yes, the address of ´i´ is not a variable, but you also cannot take the address of "the address of `i`". The address of a variable is a value, and a value is not an object, and a value does not have an address. https://en.cppreference.com/w/cpp/language/object – Frodyne Oct 13 '20 at 06:54
  • surely you can take the address of the address. That name is pointer to pointer, and the representation is `int** i` https://www.tutorialspoint.com/cplusplus/cpp_pointer_to_pointer.htm – serge Oct 14 '20 at 16:19
  • No, that is not the "address of the address". Look at their example: `var` is an `int` object, and as an object it has an address. `ptr = &var` is an `int pointer` object that contains the address of `var`, and as an object `ptr` has an address. `pptr = &ptr` is an `int pointer to pointer` object that contains the address of `ptr`, and as an object `pptr` also has an address. Notice how they only ever takes the addresses of objects. – Frodyne Oct 15 '20 at 06:22
  • If they made a second `int * ptr2 = &var; int ** pptr2 = &ptr2;` then you would have a second pointer to pointer to `var`, and `pptr != pptr2` - because `pptr` and `pptr2` do NOT contain the "address of the address" of `var` they contain the addresses of `ptr` and `ptr2`. – Frodyne Oct 15 '20 at 06:24
  • Not true. If the pointer to pointer would be just an address of an int, it would have the same notation: int*. It's not the case of int**, or int*** etc... – serge Oct 16 '20 at 08:27
0

There will be some practical limit imposed by the language implementation. The C++ standard recommends supporting at least 256 levels, but that is merely a suggestion and a lower limit does not imply non conformance of the standard.

eerorika
  • 232,697
  • 12
  • 197
  • 326