Массивы, куча и стек и типы значений



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


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

812   8  

8 ответов:

Ваш массив выделяется в куче, и ints не упакованы.

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

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

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

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

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, адрес массива,не копия массива.

да массив будет расположен в куче.

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

не коробка:

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

окна:

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

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

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

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

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

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

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

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

[ 00000000, 00000000, 00000000, F8AB56AA ]

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

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[] массив также будет храниться в куче.

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

[ 0000, 0000, 0000, 0000 ]

как стоимостью на short - это 2-байтовое число.

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

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

// 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;

массив целых чисел выделяется в куче, ни больше, ни меньше. myIntegers ссылается на начало раздела, где выделяются ints. Эта ссылка находится в стеке.

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

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

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

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

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


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

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

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

выдержка:

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

  2. переменные экземпляра для ссылочного типа всегда в куче. Вот где сам объект "живет".

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

  4. каждая статическая переменная хранится в куче, независимо от того, объявлена ли она в ссылочном типе или типе значения. Существует только один слот в общей сложности независимо от того, сколько экземпляров создаются. (Там не должны быть какие-либо экземпляры, созданные для этого одного слота, чтобы существовать, хотя.) Детали того, в какой именно куче живут переменные, сложны, но подробно объясняются в статье MSDN по этому вопросу.

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

enter image description here

enter image description here

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

enter image description here

Comments

    Ничего не найдено.