Search code examples
cpointersmemory

Assign address of address of array to pointer in C


I'm new to C and trying to understand the following code

#include <stdio.h>

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

What I thought was i is a pointer to first element of the array, then '&i' must be a pointer to pointer. But here we have an assignment

int* p = &i;

C still allows it with a warning of int* differs in levels of indirections from int(*)[3]. I thought this assignment shouldn't be allowed since it kinda weird.

Then the result of *p is 1, and it differs from *(&i) which gives the address of the first element. I don't understand why it became like that. I tried to google for the warning above, assignment from pointer to address of array to int pointer, etc. but haven't found what I need.

Thank you for your time.


Solution

  • What I thought was 'i' is a pointer to first element of the array ...

    Not quite correct. The variable i may decay to a pointer to the first element under many circumstances, but i itself is the integer array {1, 2, 3}.

    And, since there is no padding before the 1, the address of i is the same as the address of that 1. The warning appears because you're using the pointer in a way that violates a constraint in the standard(1).

    The ISO C17 standard (see 6.3.2.1 Lvalues, arrays, and function designators) has this to say:

    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.

    This is very much a "unary & operator" scenario hence it does not decay.

    The reason why *(&i) does not cause a similar issue is because:

    • The i variable is an lvalue (all array names are, they're just not modifiable lvalues that you can assign to) and it's type is therefore the full array;
    • There's a footnote in the standard that states (irrelevant detail removed): "It is always true that if E is an lvalue that is a valid operand of the unary & operator, *&E is an lvalue equal to E" - hence its type is also the full array;
    • However, since you evaluate that full array from the previous bullet point in a context that does not prevent decay (giving it to sizeof or &), it decays to the pointer.

    In other words, the second and third lines below are equivalent, both of them decay before assigning and will not produce the warning you see with the non-decaying first and fourth lines. That fourth line is simply the exact same operation that was used to assign to p1 but with *&i rather than just i, both having the same (array) type:

    int *p1 =     &i  ;  // no decay.
    int *p2 =      i  ;  // decay.
    int *p3 =   *(&i) ;  // decay.
    int *p4 = &(*(&i));  // no decay.
    

    (1) As per C17 6.5.16 Assignment operators (and 6.7.9 Initialization which references the former), the constraints do not allow for assignment or initialisation where the source object is an array. The only types are arithmetic, structure or union, or pointer. Arrays are an aggregate type, like structures and unions, but are not explicitly included.