Типы массивов, кучи, стека и значений

int[] myIntegers;
myIntegers = new int[100];

В приведенном выше коде есть новый int [100], генерирующий массив в куче? Из того, что я читал на CLR через С#, ответ да. Но я не могу понять, что происходит с фактическим int внутри массива. Поскольку они являются типами значений, я бы предположил, что их нужно будет вставить в коробку, так как я могу, например, передать myIntegers в другие части программы, и это загромождает стек, если они останутся на нем все время, Или я ошибаюсь? Я предполагаю, что они просто будут помещены в бокс и будут жить в куче столько, сколько бы существовал массив.

Ответы

Ответ 1

Ваш массив выделяется в куче, а int не помещаются в коробку.

Источником вашей путаницы, вероятно, является то, что люди говорят, что ссылочные типы выделяются в куче, а типы значений выделяются в стеке. Это не совсем точное представление.

Все локальные переменные и параметры распределяются в стеке. Это включает как типы значений, так и ссылочные типы. Разница между ними - это только то, что хранится в переменной. Неудивительно, что для типа значения значение типа хранится непосредственно в переменной, а для ссылочного типа значение типа сохраняется в куче, а ссылка на это значение - это то, что хранится в переменной.

То же самое относится к полям. Когда память распределяется для экземпляра типа агрегата (класса или структуры), он должен содержать хранилище для каждого из его полей экземпляра. Для полей ссылочного типа в этом хранилище содержится только ссылка на значение, которое в дальнейшем будет выделено в куче. Для полей типа значения это хранилище содержит фактическое значение.

Итак, учитывая следующие типы:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

Значения каждого из этих типов потребуют 16 байт памяти (при условии 32-битного размера слова). Поле I в каждом случае занимает 4 байта, чтобы сохранить его значение, поле S занимает 4 байта, чтобы сохранить его ссылку, а поле L занимает 8 байтов, чтобы сохранить его значение. Таким образом, память для значения как RefType, так и ValType выглядит следующим образом:

 0 ┌───────────────────┐
   │        I          │
 4 ├───────────────────┤
   │        S          │
 8 ├───────────────────┤
   │        L          │
   │                   │
16 └───────────────────┘

Теперь, если у вас есть три локальные переменные в функции, типов RefType, ValType и int[], например:

RefType refType;
ValType valType;
int[]   intArray;

тогда ваш стек может выглядеть следующим образом:

 0 ┌───────────────────┐
   │     refType       │
 4 ├───────────────────┤
   │     valType       │
   │                   │
   │                   │
   │                   │
20 ├───────────────────┤
   │     intArray      │
24 └───────────────────┘

Если вы присвоили значения этим локальным переменным, например:

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

Тогда ваш стек может выглядеть примерно так:

 0 ┌───────────────────┐
   │    0x4A963B68     │ -- heap address of `refType`
 4 ├───────────────────┤
   │       200         │ -- value of `valType.I`
   │    0x4A984C10     │ -- heap address of `valType.S`
   │    0x44556677     │ -- low 32-bits of `valType.L`
   │    0x00112233     │ -- high 32-bits of `valType.L`
20 ├───────────────────┤
   │    0x4AA4C288     │ -- heap address of `intArray`
24 └───────────────────┘

Память по адресу 0x4A963B68 (значение RefType) будет выглядеть примерно так:

 0 ┌───────────────────┐
   │       100         │ -- value of `refType.I`
 4 ├───────────────────┤
   │    0x4A984D88     │ -- heap address of `refType.S`
 8 ├───────────────────┤
   │    0x89ABCDEF     │ -- low 32-bits of `refType.L`
   │    0x01234567     │ -- high 32-bits of `refType.L`
16 └───────────────────┘

Память по адресу 0x4AA4C288 (значение intArray) будет выглядеть примерно так:

 0 ┌───────────────────┐
   │        4          │ -- length of array
 4 ├───────────────────┤
   │       300         │ -- `intArray[0]`
 8 ├───────────────────┤
   │       301         │ -- `intArray[1]`
12 ├───────────────────┤
   │       302         │ -- `intArray[2]`
16 ├───────────────────┤
   │       303         │ -- `intArray[3]`
20 └───────────────────┘

Теперь, если вы передали intArray другой функции, значение, вставленное в стек, будет 0x4AA4C288, адрес массива, не копия массива.

Ответ 2

Да массив будет находиться в куче.

Внутри массива не будут помещены int. Просто потому, что тип значения существует в куче, не обязательно означает, что он будет помещен в коробку. Бокс будет возникать только тогда, когда тип значения, такой как int, присваивается ссылке объекта типа.

Например

Не вставляется:

int i = 42;
myIntegers[0] = 42;

Коробки:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

Вы также можете проверить сообщение Eric на эту тему:

Ответ 3

Чтобы понять, что происходит, вот некоторые факты:

  • Объект всегда выделяется в куче.
  • Куча содержит только объекты.
  • Типы значений либо выделены в стеке, либо часть объекта в куче.
  • Массив - это объект.
  • Массив может содержать только типы значений.
  • Ссылка на объект - это тип значения.

Итак, если у вас есть массив целых чисел, массив выделяется в куче, а целые числа, которые он содержит, являются частью объекта массива в куче. Целочисленные числа находятся внутри объекта массива в куче, а не как отдельные объекты, поэтому они не помещаются в коробку.

Если у вас есть массив строк, это действительно массив ссылок на строки. Поскольку ссылки являются типами значений, они будут частью объекта массива в куче. Если вы поместите строковый объект в массив, вы фактически поместите ссылку на строковый объект в массиве, а строка будет отдельным объектом в куче.

Ответ 4

Я думаю, что в основе вашего вопроса лежит неправильное понимание ссылочных и стоимостных типов. Это то, с чем сталкивались все разработчики .NET и Java.

Массив - это всего лишь список значений. Если это массив ссылочного типа (скажем, string[]), то массив представляет собой список ссылок на различные объекты string в куче, поскольку ссылкой является значение ссылочного типа. Внутри эти ссылки реализуются как указатели на адрес в памяти. Если вы хотите визуализировать это, такой массив будет выглядеть так в памяти (в куче):

[ 00000000, 00000000, 00000000, F8AB56AA ]

Это массив string, который содержит 4 ссылки на объекты string в куче (числа здесь шестнадцатеричные). В настоящее время только последний string фактически указывает на что-либо (память инициализируется всем нулем при назначении), этот массив будет в основном результатом этого кода в С#:

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

Вышеупомянутый массив будет в 32-битной программе. В 64-битной программе ссылки будут в два раза больше (F8AB56AA будет 00000000F8AB56AA).

Если у вас есть массив типов значений (например, int[]), то массив представляет собой список целых чисел, так как значение типа значения является самим значением (отсюда и название). Визуализация такого массива будет таковой:

[ 00000000, 45FF32BB, 00000000, 00000000 ]

Это массив из 4 целых чисел, где только второму int присваивается значение (до 1174352571, которое является десятичным представлением этого шестнадцатеричного числа), а остальные целые числа равны 0 (как я уже сказал, память инициализируется до нуля и 00000000 в шестнадцатеричном формате равно 0 в десятичном значении). Код, создавший этот массив, будет выглядеть следующим образом:

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

Этот массив int[] также будет сохранен в куче.

В качестве другого примера, память массива short[4] будет выглядеть так:

[ 0000, 0000, 0000, 0000 ]

Поскольку значение a short является 2 байтовым числом.

Если тип значения хранится, это просто деталь реализации, поскольку Эрик Липперт очень хорошо объясняет здесь, не присущий различию между значением и ссылочные типы (что является различием в поведении).

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

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

Бокс происходит только в том случае, если вы конвертируете тип значения в ссылочный тип. Эти коды кода:

object o = 5;

Ответ 5

Массив целых чисел выделяется в куче, не более, не что иное. myIntegers ссылается на начало раздела, в котором выделены int. Эта ссылка находится в стеке.

Если у вас есть массив объектов ссылочного типа, например Object, myObjects [], расположенный в стеке, будет ссылаться на кучу значений, которые ссылаются на объекты.

Подводя итог, если вы передаете myIntegers некоторым функциям, вы передаете только ссылку на место, где выделена настоящая группа целых чисел.

Ответ 6

В вашем примере кода нет бокса.

Типы значений могут находиться в куче, как и в вашем массиве int. Массив выделяется в куче, и он сохраняет ints, которые являются типами значений. Содержимое массива инициализируется значением по умолчанию (int), которое оказывается равным нулю.

Рассмотрим класс, который содержит тип значения:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

Переменная h относится к экземпляру HasAnInt, который живет в куче. Он просто имеет тип значения. Это совершенно нормально, "я" просто живет в куче, как в классе. В этом примере нет бокса.

Ответ 7

Достаточно сказано всем, но если кто-то ищет четкую (но не официальную) образец и документацию о куче, стеке, локальных переменных и статических переменных, обратитесь к полной статье Джона Скита на Память в .NET - что происходит там

Выдержки:

  • Каждая локальная переменная (т.е. объявленная в методе) сохраняется в стеке. Это включает переменные ссылочного типа - сама переменная находится в стеке, но помните, что значение переменной ссылочного типа является только ссылкой (или нулевым), а не самим объектом. Параметры метода также подсчитываются как локальные переменные, но если они объявлены с помощью модификатора ref, они не получают свой собственный слот, а используют слот с переменной, используемой в вызывающем коде. Более подробную информацию см. В моей статье о передаче параметров.

  • Переменные экземпляра для ссылочного типа всегда находятся в куче. То, что сам объект "живет".

  • Переменные экземпляра для типа значения хранятся в том же контексте, что и переменная, объявляющая тип значения. Слот памяти для экземпляра эффективно содержит слоты для каждого поля в экземпляре. Это означает (учитывая предыдущие две точки), что переменная struct, объявленная внутри метода, всегда будет находиться в стеке, тогда как структурная переменная, которая является полем экземпляра класса, будет находиться в куче.

  • Каждая статическая переменная сохраняется в куче независимо от того, объявлена ​​ли она в ссылочном типе или типе значения. Всего всего один слот независимо от того, сколько экземпляров создано. (Там не обязательно должны быть экземпляры, созданные для того, чтобы один слот существовал, хотя.) Детали того, какие именно кучи переменных живут, сложны, но подробно описаны в статье MSDN по этому вопросу.

Ответ 8

Это иллюстрации, изображающие выше ответ @P Daddy

введите описание изображения здесь

введите описание изображения здесь

И я проиллюстрировал соответствующее содержимое в моем стиле.

введите описание изображения здесь