arrOne и arrTwo - это переменные, типов
int[5] и
int[3][2]. Это на уровне C. Под капотом там указатели: компилятор знает адрес начала массива. Путаницу может вызвать то, что массивы в языке C тождественны указателям на начало с той лишь разницей, что компилятор знает его длину и всякие sizeof() сработают правильно.
Соответственно, arrOne неявно приводится к
int*, а arrTwo к
int (*)[2] (указатель на массив из 2 элементов, что в свою очередь неявно приводиться к указателю на указатель на инт).
Поэтому работает код:
int a[3][2];
int (*b)[2];
b = a;
Можно считать, что arrOne - указатель на первый элемент int, а arrTwo - указатель на первую строку int[2], по совместительству на первый элемент в первой строке:
int x = *arrOne;
int y = **arrTwo; // с одной * не сработает ибо int[2], оно же int* к int не приводиться.
int *z = arrOne+2; // указатель на 3-ий элемент
int *w = arrTwo + 1; // указатель на вторую строку (первый элемент в ней).
Но лучше на закапываться в адрессную арифметику, а воспринимать массивы, как свой отдельный тип данных. Не пишите arrOne+1, а пишите arrOne[1] и не парьтесь.
Даже для int переменных компилятор знает их адрес, так что там под капотом тоже есть указатель. Не очень большая разница.
Массив - это индексируемая область памяти. Единственное, о чем надо помнить, что массивы всегда передаются по ссылке. Их дешево передавать в функции и изменения массива внутри всегда видны снаружи.