Pointer to Pointer. How far we can go?

Viewed 194

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;
.....
6 Answers

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.

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.

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.

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.

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"

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.

Related