Массив как переменная в C — это псевдоним адреса в памяти, по которому лежит его первый элемент.
Массива ни в машинном коде, ни в памяти, строго говоря,
не существует. Существует только его «содержимое» — его элементы.
При обращении к элементу массива действия производятся машиной прямо над его содержимым по вычисленному адресу.
Самого массива — не существует.
Всякий раз, когда в С Вы работаете с массивом — компилятор использует или указатель на первый элемент массива (например, когда Вы передаете массив в функцию), или адрес первого элемента массива, а машина далее с этим работает.
Указатель в C — это переменная, содержащая адрес в памяти.
Указатель в машинном коде и памяти —
существует. Он имеет значение — число размером с разрядность машины, а значение числа — это адрес в памяти.
Указатель как число — может быть передан в регистрах, лежать на стеке, и т.д.
При обращении к байту/слову/двойному слову/… по смещению через указатель, в машинном коде указатель разыменовывается (в явном виде берется его значение=адрес) т.е. в машинном коде происходят несколько другие действия, чем в случае для массива.
Если хотите понять полностью — откройте рядом книжку по ассемблеру, компилируйте Ваши примеры с выводом промежуточного ассемблерного кода (или смотрите в дизассемблер), и аккуратно сравнивайте по шагам.
Вот, кстати, приличное описание различий на английском:
eli.thegreenplace.net/2009/10/21/are-pointers-and-arrays-equivalent-in-c/