В последнее время я читаю блог Джона Скита, рассказывающий об объекте и объектах объекта С#. Я написал следующий код, чтобы воспроизвести его эксперимент.
class Pixel
{
private byte _r;
private byte _g;
private byte _b;
public int x { get; set; }
public int y { get; set; }
public System.Windows.Media.Color Color
{
get { return System.Windows.Media.Color.FromRgb(_r, _g, _b); }
}
}
static void Main(string[] args)
{
size = 1000;
var array3 = new Pixelsize];
before = GC.GetTotalMemory(true);
for (int i = 0; i < size; i++)
{
array3[i] = new Pixel();
}
after = GC.GetTotalMemory(true);
Console.WriteLine("Pixel is {0} bytes", (after - before) / size);
}
Пока что так хорошо, программа сообщает, что "Pixel is 15 bytes"
, которая составляет 8 байт базы + 4 байта + 1 + 1 + 1 = 15 байт.
Затем я хотел знать: имеет ли экземпляр struct
те же накладные расходы, что и экземпляр class
. Поэтому я изменил Pixel
на struct
.
struct Pixel
{
private byte _r;
private byte _g;
private byte _b;
public int x { get; set; }
public int y { get; set; }
public System.Windows.Media.Color Color
{
get { return System.Windows.Media.Color.FromRgb(_r, _g, _b); }
}
}
Теперь программа сообщает, что "Pixel is 0 bytes"
. Вступив в код, я обнаружил, что after
же, что и before
. Итак, struct - тип значения, он выделяется из стека. Правильно? Кроме того, когда я проверяю регистры, " ESP
" не меняется вообще. Так что он не выделяется из стека?
Глядя на TaskManager, использование памяти демо-программы увеличивается на 8000 байт после выделения. Откуда берутся эти 8000 байтов?
И, наконец, поскольку GC не имеет доступа к распределению памяти, как я могу освободить эту память? Я попытался поместить код выделения внутри блока и надеяться, что когда array3 выйдет из сферы действия, эта память будет выпущена. Однако использование памяти не изменилось. Я получаю утечку памяти здесь?
static void Main(string[] args)
{
{
size = 1000;
var array3 = new Pixelsize];
before = GC.GetTotalMemory(true);
for (int i = 0; i < size; i++)
{
array3[i] = new Pixel();
}
after = GC.GetTotalMemory(true);
Console.WriteLine("Pixel is {0} bytes", (after - before) / size);
}
//Expect the memory to be released here, but nothing happened.
}
Когда вы выделяете Array
ссылочных типов, внутри функции. Ссылка на сам массив может храниться в предварительно выделенном кадре стека (т.е. 4/8 байт для 32/64 бит). Фактическое распределение для 1000 элементов находится в куче, опять 4/8 байт на элемент. Кроме того, экземпляры класса выделяются при вызове new Pixel()
и сохраняются в живых, поскольку их ссылка сохраняется в массиве.
Когда вы меняете его на Array
типов значений, внутри функции. Ссылка на сам массив может храниться в предварительно выделенном стеке стека (т.е. 4/8 байт для 32/64 бит). Фактическое распределение для 1000 элементов находится в куче, размер x байтов на элемент, где x - размер типа значения. Любое значение, присвоенное элементу массива, копируется, каждый байт. Ничто не ссылается на элемент массива.
Поскольку вы выделяете массив типов значений, перед вызовом before = GC.GetTotalMemory(true);
, до и после не видят разницы в распределении.
Иными словами, в случае классов выделение находится в строке array3[i] = new Pixel();
(в куче), но в случае структуры, выделение находится в строке var array3 = new Pixel[size];
Для структуры new Pixel();
использует небольшое пространство в стеке, но затем вы копируете это значение в предварительно выделенное пространство для массива в куче... и, скорее всего, повторно используете это пространство стека с каждой итерацией.
Может быть, легче думать обо всем, если вы думаете о массиве int
вместо массива Pixel
. За исключением их разницы в размере, механизм между int
и Pixel
(определенный как struct) будет таким же.