Как стать автором
Обновить

Комментарии 41

System.IO.MemoryStream stream = new System.IO.MemoryStream();
BinaryFormatter objFormatter = new BinaryFormatter();
objFormatter.Serialize(stream, new X());
stream.Length — будет его размером?
зы:
[Serializable]
class X { public int Val; }
в stream.Length вы получите намного больше
да, у меня получилось 111, что никак не вяжется с размером такого мелкого объекта )
тип в этом случае сериализуется в строку, а еще я полагаю, не все скрытые поля будут сериализованы
Сильно подозреваю, что неправильно, но:
повесить на класс атрибут [StructLayout(LayoutKind.Sequential)] и:
int size = Marshal.SizeOf(new X());
А вообще такие вещи профайлер считает :)
Marshal.SizeOf вернет в данном случае объем, занимаемый полями, т.е. 4 байта
профайлер я пытался вчера дернуть из шарпа, но не очень получилось, да и неточные он данные дает
или тут надо получать ссылку на обьект IntPtr через:
GCHandle gch = GCHandle.Alloc(new X());
IntPtr unmanagedPtr = GCHandle.ToIntPtr(gch);
потом передать её unmanaged коду на подсчет?
тоже нет, при передаче в неуправляемый код никакие внутренние структуры дотнета просто так не передаются, нет в этом смысла
да и это нарушает ограничение этого этюда, ведь на шарпе неуправляемы код не напишешь (насколько я помню ))
Эта тема вроде хорошо раскрыта в CLR via C#.
А вообще правильный ответ 12?
да, правильный ответ 12, и тему я взял именно у Рихтера )
но теория теорией, а практическое подтверждение тоже требуется
для Вас еще последующие части будут )
long aaa = GC.GetTotalMemory(true);
X t = new X();
t.Val = 5;
long bbb = GC.GetTotalMemory(true);
long ccc = bbb — aaa;
System.Windows.Forms.MessageBox.Show(ccc.ToString());

ответ: 24?
нет, поскольку GetTotalMemory дает лишь приблизительную оценку, а с большим количеством объектов ответы будут еще сильнее отличаться
собственно, правильный ответ в комментариях уже есть, но нет доказательства
Спасиб =) Понял и посмотрел описания… Было интересно.
Экземпляр этого класса занимает 12 байт. 4 байта отводится на ссылку на элемент в таблице синхронизации (если объект не участвует в блокирующих операциях, эти 4 байта равны 0), 4 на ссылку на тип объекта, последние 4 на значение Val.
Как это проверить не знаю =)
правильно, и Вы даже почти знаете всю теорию для следующего этюда, но код все равно нужен )
правильнее сказать: знаете почти всю теорию, сорри )
вы не могли бы перенести этюд в блог .net? топики из личных блогов не выходят на главную страницу Хабра
мне как раз и не нужно, чтоб он выходил на главную страницу, потому что тогда меня минусуют те, кому дотнет по барабану — похоже, я зачастил своими этюдами )
а делать закрытым тоже не хочу, так как он скорее предназначен для тех, у кого нет инвайта на хабр, нежели для тех, у кого есть
а вы на gotdotnet.ru участвуете? туда бы тоже кросспостить подобные этюды, аудитория соответствующая
нет, не участвую
но если там такие монстры, как на rsdn.ru, то я пока воздержусь
там у них такие этюды, которые с решением не сразу-то поймешь
монстры никогда не составляют всю аудиторию
есть там и начинающие и всякие, а читателей, думаю, вообще полный зоопарк
просто сейчас ресурсу нужны интересные материалы, он переживает реинкарнацию и новое воплощение :-)
unsafe
{
object obj = new X();
RuntimeTypeHandle tHandle = obj.GetType().TypeHandle;
int size = *(*(int**)&tHandle + 1);
}

для работы надо разрешить запуск небезопасного кода в проекте.
и да и нет
да — потому что Вы первый догадались применить ансейф код, и Ваш метод даже дает правильные резулататы
нет — потому что Вы используете то, что истинный размер указан где-то в типе. это специфическая деталь реализации, и она может менятся, а может и вообще врать.
ну и вдобавок требуется указать содержимое полей
А что должно быть на выходе — размер экземпляра, или значения полей?
и то и другое
Я знаю как можно «не совсем честно» добраться до экземпляра класса:

X obj = new X();

fixed (int* ptr = &obj.Val)
{
int val = ptr[0];
int typeHandle = ptr[-1];
int syncBlock = ptr[-2];
}

сойдёт за ответ? =)
нет, по нескольким причинам
во-1, Вы полагаете, что объект внутри содержит три 32битных поля, а это еще надо доказать
во-2, Вы почему-то решили, что дополнительные поля находятся перед «явным» полем, а это весьма сомнительное утверждение

но направление выбрано правильное, продолжайте думать )
совершенно не понятно, каким образом я смогу доказать, что syncBlock — это именно 4-хбайтовое значение. Имхо, об этом можно только прочитать, либо догадаться, проведя некоторое количество экспериментов. Или где-то в глубине typehandle-ов всё-таки есть полные метаданные о class-layout-е, в т.ч. и о вспомогательных полях?
нет, нужно доказать, что их именно три, а не четыре и не пять
ну и вывести их значение
их смысл будет проверятся в следующем этюде
похоже, Вы правы, а я ошибся
действительно, ptr[-1] и ptr[-2] будут искомыми полями
я опирался не на адрес поля &obj.Val, а на значение, содержаееся непосредственно в ссылке obj. но похоже, она указывает в «середину» объекта, а именно — в ptr[-1] в Вашей нотации
размер реальной памяти Вы не сосчитали, но это уже сделано в комментариях ниже.

но Вы оба опирались на адрес переменной Val. если бы переменная была приватной и имела геттер/сеттер, или если бы даже ее вообще не было, Вы бы смогли тогда достучаться до этих полей?
Таксс =)) ясно (правда так и не пришла мысль, как же верно реализовать)… Жалко мне пора уходить и нет времени дальше эксперементировать. Надеюсь все-таки увидеть решение, когда вернусь. Ещё раз спасибо за этюды, в день раз их просматривать-самое оно =)
Думаю нужно сделать 2 new и вычесть ссылки. Желательно сделать это много раз и взять минимум из-за возможности сборки мусора и создания объектов в других потоках.
код давайте, код! ))
unsafe
{
    X x1 = new X() { Val = 1 };
    X x2 = new X() { Val = 2 };

    fixed (int *p1 = &x1.Val)
    fixed (int *p2 = &x2.Val)
    {
        long fieldCount = p2 - p1;
        long size = fieldCount * sizeof(int);
        Console.WriteLine(size);
        int *pointer = p1 + 1;
        while (pointer <= p2)
        {
            Console.WriteLine(*pointer);
            pointer++;
        }
    }
}


Правда тут ограничения есть.
1. Предполагается, что объекты будут расположены подряд (если запускать этот код отдельно, не создавая ни чего до него и во время него, то так оно и будет).
2. По поводу количества полей. Опять же предполагается, что они int.

А так, размер и содержимое памяти выводятся.
очень хорошая попытка, но к сожалению, не засчитывается

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

а вот содержимое выведено неправильно
см. iaroshenko.habrahabr.ru/blog/77275/#comment_2250134
Казалось бы, никто не гарантирует, что объекты в памяти будут расположены последовательно, разве нет?
а давайте подумаем, откуда могут появится дырки между объектами? только от удаленных объектов, не так ли?
кроме того, разве сборщик мусора не перемещает объекты в начало кучи?
Т.е. аллокатор памяти выделяет её всегда последовательно? Буду знать.
вопрос динамического выделения памяти — отдельная большая тема, в двух словах ее никак не опишешь
если вкратце, основная проблема — утилизация уже освобожденной памяти (т.е. повторное использование участков, которые были ранее выделены и потом освободились), поэтому объекты желательно держать как можно плотнее. в неуправляемых языках, типа с или с++ (ну также и собственно на уровне ос происходит, хотя с++ дублирует этот механизм) выделяемая память имеет указатель размера, занимаемого объектом. при освобождении указатель вместе с размером добавляется в т.н. список свободных областей. есть хорошая статья джоэля, затрагивающая эту тему: russian.joelonsoftware.com/Articles/BacktoBasics.html. она будет полезна любому, на мой взгляд
в управляемой среде .NET ситуация принципиально другая. поскольку инфраструктура .NET контролирует каждую ссылку на любой объект, имеется возможность перемещать объекты из одной области памяти в другую прозрачно для пользователя, обновляя при этом все ссылки. сборщик мусора действует именно так, уплотняя объекты как можно сильнее — чтобы свободная память была участком максимального размера.
кроме того, следует упомянуть о поколениях сборщика мусора (объекты в пределах поколения «группируются»), о выравнивании памяти (для эффективности границы объектов должны быть выровнены по некоторому размеру, обычно размеру указателя), о больших объектах (в .NET большие объекты не переносятся с места на место, ибо это накладно, а располагаются в отдельной области управляемой кучи).
все это очень большая тема )
пролистал Рихтера (CLS via C#). он прямым текстом пишет «в управляемой куче последовательно созданные объекты гарантированно будут расположены друг за другом»
конечно, ремарки насчет выравнивания, поколений и больших объектов остаются в силе
using System;
using System.Runtime.InteropServices;

public static class ObjectExtensions
{
  [StructLayout(LayoutKind.Explicit)]
  internal struct MyStruct
  {
    [FieldOffset(0)]
    public Int32 Value1;

    [FieldOffset(4)]
    public object Value2;
  }

  public static unsafe int GetAddr(this object o)
  {
    MyStruct ms = new MyStruct();
    ms.Value2 = o;
    int* ptr = &ms.Value1;
    return ptr[1];
  }

  public static unsafe void PrintObject(this int ptr, int offset, int count, string descr)
  {
    Console.WriteLine("Printing {0} object at addr : {1}", descr, ptr);
    int* ptr2 = (int*)ptr;
    for (int i = 0; i < count; ++i)
    {
      Console.WriteLine("-- {0}", ptr2[offset + i]);
    }
  }
}

class X
{
  public int Val {get; set; }
}

class App
{
  static unsafe void Main()
  {
    const int INT_BYTES = 4;
    object x = new X() { Val = 101 };
    {
      object y = new X() { Val = 257 };
      int size = y.GetAddr() - x.GetAddr();
      Console.WriteLine("Size is {0} bytes", size);
      x.GetAddr().PrintObject(-1, size/INT_BYTES, "x");
      y.GetAddr().PrintObject(-1, size/INT_BYTES, "y");
    }
  }
}

* This source code was highlighted with Source Code Highlighter.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации