В Си я знаю, что могу динамически распределить двумерный массив в куче, используя следующий код:
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 разбирает его во время компиляции?
c
arrays
memory
data-structures
stack-memory
Крис Купер
источник
источник
malloc()
не приводят к N-мерному массиву. , Это приводит к массивам указателей [к массивам указателей [...]] для полного разделения одномерных массивов. См. Правильное размещение многомерных массивов, чтобы узнать, как распределить ИСТИННЫЙ N-мерный массив.Ответы:
Статический двумерный массив выглядит как массив массивов - он просто расположен непрерывно в памяти. Массивы - это не то же самое, что указатели, но из-за того, что вы часто можете использовать их практически взаимозаменяемо, иногда это может сбивать с толку. Тем не менее, компилятор правильно отслеживает, что делает все правильно. Вы должны быть осторожны со статическими 2D-массивами, как вы упомянули, так как если вы попытаетесь передать один из них в функцию, принимающую
int **
параметр, произойдут плохие вещи. Вот быстрый пример:По памяти выглядит так:
точно так же как:
Но если вы попытаетесь перейти
array1
к этой функции:вы получите предупреждение (и приложение не сможет получить доступ к массиву правильно):
Потому что 2D массив не совпадает с
int **
. Автоматическое затухание массива в указатель идет, так сказать, только на один уровень. Вам нужно объявить функцию как:или
Чтобы сделать все счастливым.
Эта та же самая концепция распространяется на n- мерные массивы. Однако использование такого рода забавных бизнесов в вашем приложении, как правило, только усложняет понимание. Так что будьте осторожны там.
источник
sizeof(int[100]) != sizeof(int *)
(если вы не найдете платформу с100 * sizeof(int)
байтами /int
, но это совсем другое.Ответ основан на идее, что C на самом деле не имеет 2D-массивов - у него есть массивы-массивы. Когда вы заявляете это:
Вы запрашиваете
someNumbers
массив из 4 элементов, где каждый элемент этого массива имеет типint [2]
(который сам является массивом из 2int
с).Другая часть головоломки состоит в том, что массивы всегда располагаются непрерывно в памяти. Если вы попросите:
тогда это всегда будет выглядеть так:
(4
sometype_t
объекта расположены рядом друг с другом без пробелов между ними). Итак, в вашемsomeNumbers
массиве массивов это будет выглядеть так:И каждый
int [2]
элемент сам по себе является массивом, который выглядит так:В общем, вы получите это:
источник
int
в массиве массивов (например, путем оценкиa[0]
или&a[0][0]
), тогда да, вы можете сместить его, чтобы последовательно обращаться к каждомуint
).в памяти равно:
источник
В ответ на ваш также: оба, хотя компилятор делает большую часть тяжелой работы.
В случае статически размещенных массивов, «Система» будет компилятором. Он зарезервирует память, как для любой переменной стека.
В случае с массивом malloc, «Система» будет исполнителем malloc (обычно ядра). Все, что выделит компилятор, это базовый указатель.
Компилятор всегда будет обрабатывать тип так, как он объявлен, за исключением приведенного Карлом примера, в котором он может определить взаимозаменяемое использование. Вот почему, если вы передаете [] [] функцию, она должна предполагать, что это статически распределенная квартира, где ** считается указателем на указатель.
источник
malloc
реализовано, не указано в стандарте и оставлено для реализации, соответственно. Окружающая среда. Для автономных сред это необязательно, как и для всех частей стандартной библиотеки, требующих связывания функций (это то, к чему фактически приводят требования, а не буквально то, что заявляет стандарт). Для некоторых современных размещенных сред он действительно полагается на функции ядра, либо весь материал, либо (например, Linux), как вы писали с использованием обоих, stdlib и kernel-примитивов. Для однопроцессных систем с не виртуальной памятью это может быть только stdlib.Предположим, мы
a1
иa2
определили и инициализировали, как показано ниже (c99):a1
представляет собой однородный двумерный массив с простой непрерывной компоновкой в памяти и выражением(int*)a1
вычисляется до указателя на его первый элемент:a2
инициализируется из гетерогенного двумерного массива и является указателем на значение типаint*
, то есть выражение разыменования*a2
переходит в значение типаint*
, расположение в памяти не должно быть непрерывным:Несмотря на совершенно разную структуру памяти и семантику доступа, грамматика языка C для выражений доступа к массиву выглядит одинаково как для однородного, так и гетерогенного двумерного массива:
a1[1][0]
будет извлекать значение144
изa1
массиваa2[1][0]
будет извлекать значение244
изa2
массиваКомпилятор знает, что выражение доступа для
a1
действует на типint[2][2]
, когда выражение доступа дляa2
оперирует типомint**
. Сгенерированный ассемблерный код будет следовать гомогенной или гетерогенной семантике доступа.Код обычно дает сбой во время выполнения, когда массив типа
int[N][M]
приведен к типу и затем доступен как типint**
, например:источник
Чтобы получить доступ к определенному 2D массиву, рассмотрите карту памяти для объявления массива, как показано в коде ниже:
Для доступа к каждому элементу достаточно просто передать интересующий вас массив в качестве параметров функции. Затем используйте смещение для столбца, чтобы получить доступ к каждому элементу в отдельности.
источник