#include <stdio.h>

int main(void)
{
    // arr - массив из 3 int.
    int arr[] = { 10, 20, 30 };
    
    // arrptr - указатель на массив неопределенного количества int.
    int (*arrptr)[] = &arr;
    
    // intptr - указатель на первый элемент массива. & не нужен, потому что
    // см. парой строк ниже.
    int *intptr = arr;
    
    // 1. При использовании имени массива, оно всегда неявно кастится
    // к указателю на тип его элементов, если это имя не является операндом
    // для & или sizeof. Тут arr кастится к int *, но это отнюдь не означает,
    // что arr является int *.
    
    // Мы делаем каст аргумента в void *, чтобы осчастливить компилятор.
    // Это требуется лишь для %p по стандарту, но в обычном мире на
    // существующих машинах он совершенно ничего не делает со
    // значением указателя).
    printf("Address of the first element "
        "(using imlicit cast of arr): %p\n", (void *) arr);
    
    // 2. Тут все просто? Хуй там. Тут arr кастится к int *, потом [0] делает
    // адресную арифметику (address + sizeof(int) * 0), но так как слева &
    // то дереференса не происходит.
    printf("Address of the first element "
        "(using &arr[0]): %p\n", (void *) &arr[0]);

    // 3. Адрес массива в виде указателя на массив численно равен указателю
    // на первый элемент.
    printf("Address of arr using & directly (&arr): %p and from arrptr value: %p\n",
        (void *) &arr, (void *) arrptr);
    
    // Раз адрес массива совпадает с адресом его первого элемента, то
    // дереференс указателя на массив не поменяет численное значение
    // этого указателя (на нормальных машинах), а поменяет только его
    // тип.
    printf("Dereferencing pointer to an array = %p changes its type\n"
        "\tbut not its value: %p\n", (void *) &arr, (void *) *(&arr));
    
    // Читаем через указатель на int, скучно.
    printf("Dereferencing intptr (int *): %d\n", *(intptr));
    
    // См. (1), плюс дереференс.
    printf("Dereferencing arr implicitly casted to (int *): %d\n", *arr);
    
    // См. (2), но [] делает дереференс. Т.е, *((int *) (arr)) + 0).
    printf("Using [] sugar on arr: %d\n", arr[0]);
    
    // Опять первый дереференс указателя на массив меняет его тип с
    // "указателя на массив int" на "указатель на int", а второй уже
    // явно (*) или неявно ([]) дереференсит и читает значение.
    printf("Dereferencing &arr twice: %d\n", *(*(&arr)));
    printf("Dereferencing arrptr twice: %d\n", (*arrptr)[0]);
    
    // Все вышесказанное справедливо для обычных машин и обычного кода.
    // В теории может существовать компилятор, соблюдающий стандарт, у
    // которого указатели различных типов не будут равны, у которого явно
    // взятый указатель на массив не будет совпадать с указателем на первый
    ///элемент. Т.е., хотя так никто не делает (потом что не нужно), но
    // компилятор, у которого вот такое поведение, вполне нормален:
    //     (uintptr_t) (&arr) != (uintptr_t) (&arr[0])
    // но при этом неявный каст по стандарту обязан работать везде:
    //     (uintptr_t) (arr) == (uintptr_t) (&arr[0])
}
