Как в памяти форматируются многомерные массивы?
-
23-09-2019 - |
Вопрос
Я знаю, что в C я могу динамически разместить двумерный массив в куче, используя следующий код:
int** someNumbers = malloc(arrayRows*sizeof(int*));
for (i = 0; i < arrayRows; i++) {
someNumbers[i] = malloc(arrayColumns*sizeof(int));
}
Очевидно, что на самом деле это создает одномерный массив указателей на группу отдельных одномерных массивов целых чисел, и «Система» может понять, что я имею в виду, когда спрашиваю:
someNumbers[4][2];
Но когда я статически объявляю 2D-массив, как в следующей строке...:
int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];
... создается ли в стеке аналогичная структура или она имеет совершенно другую форму?(т.е.это одномерный массив указателей?Если нет, то что это такое и как выясняются ссылки на него?)
Кроме того, когда я сказал «Система», что на самом деле отвечает за это?Ядро?Или компилятор C разбирается во время компиляции?
Решение
Статический двумерный массив выглядит как массив массивов — он просто расположен в памяти последовательно.Массивы — это не то же самое, что указатели, но поскольку их часто можно использовать как взаимозаменяемые, иногда это может сбить с толку.Однако компилятор отслеживает правильно, благодаря чему все хорошо выстраивается.Вы должны быть осторожны со статическими 2D-массивами, как вы упомянули, поскольку, если вы попытаетесь передать его в функцию, принимающую int **
параметр, произойдут плохие вещи.Вот краткий пример:
int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};
В памяти выглядит так:
0 1 2 3 4 5
точно такой же как:
int array2[6] = { 0, 1, 2, 3, 4, 5 };
Но если вы попытаетесь пройти array1
к этой функции:
void function1(int **a);
вы получите предупреждение (и приложение не сможет правильно получить доступ к массиву):
warning: passing argument 1 of ‘function1’ from incompatible pointer type
Поскольку 2D-массив — это не то же самое, что int **
.Автоматическое преобразование массива в указатель идет, так сказать, только «на один уровень».Вам необходимо объявить функцию как:
void function2(int a[][2]);
или
void function2(int a[3][2]);
Чтобы все было счастливо.
Эта же концепция распространяется и на н-мерные массивы.Однако использование подобных забавных возможностей в вашем приложении обычно только усложняет понимание.Так что будьте осторожны там.
Другие советы
Ответ основан на идее, что C на самом деле не иметь 2D-массивы - есть массивы массивов.Когда вы заявляете об этом:
int someNumbers[4][2];
Вы просите someNumbers
быть массивом из 4 элементов, где каждый элемент этого массива имеет тип int [2]
(который сам по себе является массивом из 2 int
с).
Другая часть загадки заключается в том, что массивы в памяти всегда располагаются подряд.Если вы попросите:
sometype_t array[4];
тогда это всегда будет выглядеть так:
| sometype_t | sometype_t | sometype_t | sometype_t |
(4 sometype_t
объекты, расположенные рядом друг с другом, без промежутков между ними).Итак, в вашем someNumbers
массив массивов, это будет выглядеть так:
| int [2] | int [2] | int [2] | int [2] |
И каждый int [2]
элемент сам по себе является массивом, который выглядит следующим образом:
| int | int |
В общем, вы получаете следующее:
| int | int | int | int | int | int | int | int |
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};
в памяти равно:
unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};
В ответ на ваше также:И то, и другое, хотя большую часть тяжелой работы берет на себя компилятор.
В случае статически выделенных массивов компилятором будет «Система».Он зарезервирует память, как и для любой переменной стека.
В случае с массивом malloc «Система» будет реализатором malloc (обычно ядро).Все, что компилятор выделит, — это базовый указатель.
Компилятор всегда будет обрабатывать тип так, как он объявлен, за исключением примера, приведенного Карлом, где он может определить взаимозаменяемое использование.Вот почему, если вы передаете [][] функции, она должна предполагать, что это статически выделенный плоский объект, где ** считается указателем на указатель.
Чтобы получить доступ к конкретному 2D-массиву, рассмотрите карту памяти для объявления массива, как показано в коде ниже:
0 1
a[0]0 1
a[1]2 3
Чтобы получить доступ к каждому элементу, достаточно просто передать в функцию в качестве параметров интересующий вас массив.Затем используйте смещение для столбца для доступа к каждому элементу индивидуально.
int a[2][2] ={{0,1},{2,3}};
void f1(int *ptr);
void f1(int *ptr)
{
int a=0;
int b=0;
a=ptr[0];
b=ptr[1];
printf("%d\n",a);
printf("%d\n",b);
}
int main()
{
f1(a[0]);
f1(a[1]);
return 0;
}