Can a pointer point to itself?

Viewed 177

I'm trying to understand something in 2D-arrays.

int A[2][3] = {{20, 30, 40}, {50, 60, 70}};

printf("%p\n",A);  // Output is 0x7ffde7e966d0
printf("%p\n",*A); // Output is 0x7ffde7e966d0

I understand that these two addresses have different scope and A + 1 is completely different address than *A + 1, but my main misunderstanding is when using the dereference operator it should produce a value not an address. It looks like the name of the 2D-array is acting like a pointer that points to itself. I tried this code

int *p = &p;
printf("%p\n",p);  // Output is 0xfff1c9a94b0
printf("%p\n",*p); // Output is 0x1c9a94b0

the compiler gives me a warning initialization of ‘int *’ from incompatible pointer type ‘int **’ [-Wincompatible-pointer-types] but the code runs as above shown. If i tries to add more asterisks int **p = &p; the compiler warning will be like ``int **’ from incompatible pointer type ‘int *** and the strange thing is the output address is always the same for the first 8 or 7 digits from right to left. in this case can we call *p is a pointer that points to it self ?

6 Answers

For the statements:

printf("%p\n",A);  // Output is 0x7ffde7e966d0
printf("%p\n",*A); // Output is 0x7ffde7e966d0

The first thing to note is that this invokes undefined behavior because %p specifier expects a void pointer, the correct code would be:

printf("%p\n",(void*)A);  // Output is 0x7ffde7e966d0
printf("%p\n",(void*)*A); // Output is 0x7ffde7e966d0

Secondly the above statements will result in the same address because the address of A will be that of its first element which is A[0] wich is really the the same as *(A + 0) wich is equivalent to *A, which in turn is also a pointer to the first element of the array, A[0][0].

Essentially the addresses are the same, &A is the same as &A[0] which is the same as &A[0][0].

For :

int *p = &p;
printf("%p\n",p);  // Output is 0xfff1c9a94b0
printf("%p\n",*p); // Output is 0x1c9a94b0

The behavior of the above snippet is also undefined and will always be no matter the levels of indirection (asterisks) you add to it, you will always have a type mismatch because you are assigning the address of a variable of the same type (unless you cast it). The explanation for the value you are getting, in this particular case, could be attributed to the difference in size between int which is usually 4 bytes and int* which is usually 8 bytes, but again, undefined behavior is undefined, and there is no accurate diagnose for it, the result can be anything.

Your printf calls are somewhat incorrect: %p expects a void *, which neither A nor *A are, even after decaying to a pointer to their first element.

Consider this simple example:

#include <stdio.h>

int main() {
    int A[2][3] = {{20, 30, 40}, {50, 60, 70}};

    printf("&A=%p  &A+1=%p  sizeof(*&A)=%zu\n", (void*)(&A), (void*)(&A + 1), sizeof(*&A));
    printf(" A=%p   A+1=%p  sizeof( *A)=%zu\n", (void*)( A), (void*)( A + 1), sizeof( *A));
    printf("*A=%p  *A+1=%p  sizeof(**A)=%zu\n", (void*)(*A), (void*)(*A + 1), sizeof(**A));
    return 0;
}

On my system, the output is:

&A=0x7fff5b9877a0  &A+1=0x7fff5b9877b8  sizeof(  A)=24
 A=0x7fff5b9877a0   A+1=0x7fff5b9877ac  sizeof( *A)=12
*A=0x7fff5b9877a0  *A+1=0x7fff5b9877a4  sizeof(**A)=4

The difference between the address produced for &A and &A + 1 is sizeof(*&A), which is the same as sizeof(A)... and the same for the next 2 statements.

A is an array of 2 arrays of 3 ints. In memory it is implemented as a sequence of 24 bytes, 4 for each of the 6 integers (on a system with 32-bit int and 8-bit bytes, the most common values today). These 24 bytes are located at a specific address in memory determined by the definition location. The array A, its first element A[0] (is itself an array that can be referred to as *A, or even 0[A]) and A[0][0] are all located at the same place, the address of the first byte of the first int.

According to the C Standard (6.3.2.1 Lvalues, arrays, and function designators)

3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

Consider the following demonstration program.

#include <stdio.h>

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

    printf( "sizeof( a ) = %zu\n", sizeof( a ) );
    printf( "sizeof( a + 0 ) = %zu\n", sizeof( a + 0 ) );
}

Its output might look like

sizeof( a ) = 20
sizeof( a + 0 ) = 8

In the expression a + 0 the array designator a is implicitly converted to pointer to its first element. That is the expression has the type int *.

In your code snippet

int A[2][3] = {{20, 30, 40}, {50, 60, 70}};

printf("%p\n",A);  // Output is 0x7ffde7e966d0
printf("%p\n",*A); // Output is 0x7ffde7e966d0

the expression *A that is the same as A[0] yields the first element of the array A. It has the type int[3]. But used as a function argument this one-dimensional array is implicitly converted to pointer of the type int * that points to its first element. In fact it is equivalent to the expression &A[0][0].

As for your question whether a pointer can be initialized by its address then a pointer of the type void * can be initialized such a way.

Here is a demonstration program.

#include <stdio.h>

int main( void )
{
    void *p = &p;

    printf( "p = %p\n", p );
    printf( "&p = %p\n", &p );
}

The program output is

p = 0x7fff3bef8dd8
&p = 0x7fff3bef8dd8

For a pointer of the type int * you need to use casting like

int *p = ( int * )&p;

or

int *p = ( void * )&p;

There are two possibilities to have a pointer point to itself.

The first is void *p = &p; and the second is used e.g. with linked lists, trees and other structures to point to items of the same type.

Consider:

struct List {
    struct List *next;
    int value;
};

struct List element;
element.next = &element;

It also happens, that the numerical value of &element.next matches the numerical value of &element, however the full types do not match. The first is of type struct List ** and the other if struct List*.

Same happens when trying to assign size_t *p = &p; or intptr_t *p = &p which should be able to store the full numeric value of the address, but the types still conflict.

If you want to produce a value, use the appropriate formatting string and type cast:

int A[2][3] = {{20, 30, 40}, {50, 60, 70}};
A[0][0]=1;
        
printf("%p\n",A);  // Output is 0x7ffde7e966d0
printf("%p\n",*A); // Output is 0x7ffde7e966d0
printf("%d\n",*(int *)A); // Output is 1

In the case of:

int *p = &p;

you are just basically doing this:

int *p
p=&p;

which speaks for itself: it's the address of p in the stack

Given your declarations, your array looks like this in memory (assuming a 4-byte int):

addr        Expression type
offset      
            int      int [3]    int [2][3]
----        ---      -------    ----------
     +====+
0x00 | 20 | A[0][0]   A[0]      A
     +––––+
0x04 | 30 | A[0][1]
     +––––+
0x08 | 40 | A[1][2]
     +====+
0x0c | 50 | A[1][0]   A[1]
     +––—–+
0x10 | 60 | A[1][1]
     +––––+
0x14 | 70 | A[1][2]
     +====+

There's no metadata for array size, or for the location of the first element, or anything like that stored as part of the array - it's just a sequence of objects.

The key thing to notice is that the address of an array is the same as the address of its first element - the address of A is the same as the address of A[0], which is the same as the address of A[0][0]. The expressions &A, &A[0], and &A[0][0] will all yield the same address value (although the types of each expression will be different, which may affect value representation)1.

Unless it is the operand of the unary & operator, an expression of type "N-element array of T" is converted, or "decays", to an expression of type "pointer to T" and the value of the expression is the address of the first element in the array.

The expression A has type "2-element array of 3-element array of int" (int [2][3]). Unless it is the operand of the unary & operator, it "decays" to type "pointer to 3-element array of int" (int (*)[3]) and its value is the address of A[0]. The expression A[0] has type "3-element array of int" (int [3]). Unless it is the operand of the unary & operator, it "decays" to type "pointer to int" (int *) and its value is the address of A[0][0].

Why is that? Array indexing in C is based on pointer arithmetic. The expression a[i] is defined as *(a + i) - given an address value a, offset i elements (not bytes!) from that address and dereference the result. This is taken from the B programming language from which C is derived. The problem is that B set aside an explicit pointer to the first element of the array - the declaration

auto A[N];

created the following in memory:

   +---+
A: |   | ---+
   +---+    |
    ...     |
     +------+
     |
     v
   +---+
   |   | A[0]
   +---+
   |   | A[1]
   +---+
    ...

Ritchie wanted to keep B's array behavior - a[i] == *(a + i) - but he didn't want to keep the explicit pointer that behavior required. So he created the rule that anytime the compiler sees an array expression that's not the operand of the unary & operator, it will convert the expression to a pointer.

If A "decays" to an expression of type int (*)[3] and is the address of A[0], then it follows that the expression *A has type int [3] (which in turn "decays" to int *) and is the value of A[0]:

 A == &A[0] // int (*)[3] == int (*)[3]
*A ==  A[0] // int [3] == int [3] ==> int * == int *
  

Here it is in handy table form:

Expression    Type             Decays to     Equivalent Expression
––––––––––    ––––             –––––––––     –––––––––––––––––––––
         A    int [2][3]       int (*)[3]    &A[0]
        &A    int (*)[2][3]    n/a           n/a
        *A    int [3]          int *         A[0]
      A[i]    int [3]          int *         &A[i][0]
     &A[i]    int (*)[3]       n/a           n/a
     *A[i]    int              n/a           A[i][0]

  1. Pointers to different types don't have to all have the same size or representation - that depends on the implementation. For modern server and desktop architectures like x86/x86-64, all object pointer types have the same size as the native word size (32 or 64 bits). However, the language definition only requires the following:
    • char * and void * have the same size and alignment;
    • Pointers to qualified types have the same size and alignment as pointers to their unqualified equivalents (e.g., sizeof (const int) == sizeof (volatile int) == sizeof (int))
    • All struct pointer types have the same size and alignment;
    • All union pointer types have the same size and alignment;
Related