Как хранятся в памяти многомерные массивы?

Раньше я думал, что когда мы создаем многомерный массив, например

int arr[2][3] = 
{
    {1, 2, 3},
    {1, 2, 3}
}

в памяти выделяется две ячейки, каждая из которых занимает sizeof(int) * 3 и таким образом можно легко обращаться к любому элементу. Например при обращении к последней тройке arr[1][2], на уровне указателей это будет выглядеть так *(arr + (sizeof(int) * 3 * 1) + 2). Ну и памяти занимает такой массив ровно 2х3.

Но затем я увидел такую формулу на уровне указателей arr[1][2] => *(*(arr + 1) + 2) и пришел к выводу, что многомерный массив хранится в памяти следующим образом (здесь я буду обозначать память вот так 0[5], где 0 - адрес ячейки, [5] - значение, которое хранится внутри ячейки): 0[адрес 2], 1[адрес 5], 2[1], 3[2], 4[3], 5[1], 6[2], 7[3]. И при таком хранении можно легко понять почему в этой формуле *(*(*(arr + i) + j) + k) на каждом уровне идет разыменование. Однако при таком способе хранения тот же массив уже занимает 2 + 2x3.

Так как хранятся многомерные массивы и если описанный мной второй способ верный, то в чем его преимущество над первым?


Ответы (1 шт):

Автор решения: HolyBlackCat

Нет, для массива в памяти хранятся только его элементы, и никаких указателей. int arr[2][3] занимает только sizeof(int) * 2 * 3 байт места.

Но, одновременно с этим, формула arr[1][2] => *(*(arr + 1) + 2) - правильная.

Фокус в том, что нужные указатели вычисляются на лету. Во многих ситуациях массивы автоматически преобразуются в указатель на свой первый элемент, в том числе в этом примере. Получающиеся указатели вычисляются на ходу, и не хранятся постоянно в памяти.

Еще, нужно понимать, что многомерные массивы - это просто обычные (одномерные) массивы, но у которых тип элемента - тоже массив. Для них нет каких-то специальных правил.

Рассмотрим *(*(arr + i) + j).

  • + сначала преобразует массив arr (типа int[2][3]) в указатель на его первый элемент (как если бы вы сделали &arr[0]), типа int (*)[3] ("указатель на массив из 3 int-ов").

  • Затем + i сдвигает указатель на i * sizeof(тип-элемента) байт, где тип элемента - int[3], а sizeof(int[3]) == sizeof(int) * 3.

  • Затем * разыменовывает указатель, и результат имеет тип int[3] (массив из 3 int-ов).

  • Дальше все то же самое повторяется для второго индекса. + превращает массив в указатель типа int *, и т.д.


в памяти выделяется две ячейки, каждая из которых занимает sizeof(int) * 3

Я надеюсь вы понимаете, что элементы массива идут в памяти подряд, поэтому вместо "две ячейки по sizeof(int)*3" можно было бы с тем же успехом сказать "6 ячеек по sizeof(int)". Это просто 6 int-ов подряд.

→ Ссылка