Как примитивные типы определяются нерекурсивно?

Поскольку struct в C# состоит из битов своих членов, у вас не может быть типа значения T, который включает какие-либо поля T:

// Struct member 'T.m_field' of type 'T' causes a cycle in the struct layout
struct T { T m_field; }

Насколько я понимаю, экземпляр вышеуказанного типа никогда не может быть создан* — любая попытка сделать это приведет к бесконечному циклу создания/выделения памяти (что, как я полагаю, приведет к переполнению стека?** ) — или, с другой стороны, другой взгляд на это может заключаться в том, что само определение просто не имеет смысла; возможно, это саморазрушающаяся сущность, что-то вроде «Это утверждение ложно».

Любопытно, однако, если вы запустите этот код:

BindingFlags privateInstance = BindingFlags.NonPublic | BindingFlags.Instance;

// Give me all the private instance fields of the int type.
FieldInfo[] int32Fields = typeof(int).GetFields(privateInstance);

foreach (FieldInfo field in int32Fields)
{
    Console.WriteLine("{0} ({1})", field.Name, field.FieldType);
}

... вы получите следующий вывод:

m_value (System.Int32)

Кажется, нас здесь "лгут"***. Очевидно, я понимаю, что примитивные типы, такие как int, double и т. д., должны быть определены каким-то особым образом глубоко в недрах C# (вы не можете определить все возможные единицы внутри системы в терминах этой системы... не так ли?— другая тема, независимо!); Мне просто интересно знать, что здесь происходит.

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

Черная магия?


*Отдельное примечание: подходит ли это слово для типа значения ("экземпляр")? Я чувствую, что это несет в себе «референсные» коннотации; но, может быть, это только я. Кроме того, мне кажется, что я мог задавать этот вопрос раньше — если да, то я забываю, что люди отвечали.

**Оба Мартин против Лёвиса и Эрик Липперт указали что это не совсем точно и не является подходящим взглядом на проблему. См. их ответы для получения дополнительной информации.

***Хорошо, я понимаю, что на самом деле никто не лжет. Я не имел в виду, что считаю это ложным; я подозревал, что это было каким-то чрезмерным упрощением. После того, как я понял (я думаю) ответ thecoop, мне он кажется более логичным.


person Dan Tao    schedule 20.01.2011    source источник
comment
Я использую свою палочку Призыва @Eric Lippert! :)   -  person Dan J    schedule 20.01.2011
comment
@djacobson - похоже, твоя палочка работает. Могу ли я это одолжить? У меня есть несколько вещей, которые я хотел бы призвать, и это не Эрик Липперт...   -  person Oded    schedule 21.01.2011
comment
Чтобы понять рекурсию, вы должны сначала понять рекурсию.   -  person Jwosty    schedule 08.08.2015


Ответы (3)


Насколько мне известно, внутри сигнатуры поля, которая хранится в сборке, есть определенные жестко закодированные шаблоны байтов, представляющие «основные» примитивные типы — целые числа со знаком/без знака и числа с плавающей запятой (а также строки, которые являются ссылочными типами и частный случай). CLR изначально знает, как с ними справляться. Ознакомьтесь с Разделом II, разделом 23.2.12 спецификации CLR, чтобы узнать о битовых шаблонах подписей.

Внутри каждой примитивной структуры ([mscorlib]System.Int32, [mscorlib]System.Single и т. д.) в BCL есть одно поле этого собственного типа, и поскольку структура имеет точно такой же размер, как и составляющие ее поля, каждая примитивная структура представляет собой тот же битовый шаблон, что и ее собственный тип в памяти. , поэтому их можно интерпретировать либо CLR, компилятором C#, либо библиотеками, использующими эти типы.

В C# int, double и т. д. являются синонимами структур mscorlib, каждая из которых имеет свое примитивное поле типа, изначально распознаваемого средой CLR.

(Здесь есть дополнительная сложность, поскольку спецификация CLR указывает, что любые типы, которые имеют «краткую форму» (собственные типы CLR), всегда должны быть закодированы как эта короткая форма (int32), а не valuetype [mscorlib]System.Int32. Таким образом, компилятор C# также знает о примитивных типах, но я не уверен в точной семантике и особом регистре, которые используются в компиляторе C# и CLR, скажем, для вызовов методов в примитивных структурах)

Итак, согласно теореме Гёделя о неполноте, должно быть что-то «вне» системы, с помощью которой ее можно определить. Это магия, которая позволяет CLR интерпретировать 4 байта как собственный int32 или экземпляр [mscorlib]System.Int32, который является псевдонимом C#.

person thecoop    schedule 20.01.2011
comment
Таким образом, в основном вы говорите, что, например, тип System.Int32 состоит из нестандартного field, чей (собственный) тип находится вне BCL; но это поле представлено в соответствующем объекте Type как имеющее тип Int32 (хотя это и не так). Это точно? - person Dan Tao; 20.01.2011
comment
Вроде, как бы, что-то вроде. Это не нестандартное поле, это обычное int32. CLR умеет интерпретировать поля этого типа как 32-разрядное целое число. Структура [mscorlib]System.Int32 совместима в двоичном виде с нативной структурой int32 благодаря тому, как работают структуры. По сути, именно Magic останавливает рекурсию. - person thecoop; 20.01.2011
comment
Думаю, я понимаю, о чем вы говорите. Другими словами, исходя из фактических битов, составляющих поле, Int32 (из mscorlib) неотличим от собственного 32-битного целого числа. Таким образом, значение Int32 может содержать поле, которое само является Int32 с точки зрения его битов; но рекурсии нет, потому что CLR распознает это как биты, представляющие собственный тип. Пожалуйста, поправьте меня, если я все еще ошибаюсь, поскольку я хочу быть уверен, что правильно понимаю, о чем, черт возьми, я говорю! - person Dan Tao; 20.01.2011
comment
@Andras: это очень похоже на AFAIK (и понимаю), я не разработчик CLR! Потребовался бы кто-то, кто работал рядом с кодом, чтобы подтвердить или опровергнуть семантику, как я ее здесь понимаю. - person thecoop; 20.01.2011
comment
@thecoop: В таком случае, я думаю понял ;) Спасибо, этот ответ был весьма полезным. Я также очень ценю, что вы притащили сюда теорему Гёделя о неполноте! - person Dan Tao; 21.01.2011

Насколько я понимаю, экземпляр вышеуказанного типа никогда не может быть создан, любая попытка сделать это приведет к бесконечному циклу создания/выделения (что, как я полагаю, вызовет переполнение стека?) - или, альтернативно, другой способ взглянуть на может быть, само определение просто не имеет смысла;

Это не лучшая характеристика ситуации. Лучший способ взглянуть на это состоит в том, что размер каждой структуры должен быть четко определен. Попытка определить размер T переходит в бесконечный цикл, и поэтому размер T не определен четко. Следовательно, это недопустимая структура, поскольку каждая структура должна иметь четко определенный размер.

Кажется, нас здесь обманывают

Нет никакой лжи. Int — это структура, содержащая поле типа int. Int имеет известный размер; это по определению четыре байта. Следовательно, это допустимая структура, поскольку известен размер всех ее полей.

Как тип System.Int32 (например) фактически хранит 32-битное целочисленное значение

тип ничего не делает. Тип — это просто абстрактное понятие. За хранение отвечает CLR, и она делает это, выделяя четыре байта пространства в куче, стеке или регистрах. Как еще, по вашему мнению, будет храниться четырехбайтовое целое число, если не в четырех байтах памяти?

как объект System.Type, на который ссылается typeof(int), представляет себя так, как будто это значение само является полем повседневного экземпляра, типизированным как System.Int32?

Это просто объект, написанный в коде, как и любой другой объект. В этом нет ничего особенного. Вы вызываете у него методы, он возвращает больше объектов, как и любой другой объект в мире. Почему вы думаете, что в этом есть что-то особенное?

person Eric Lippert    schedule 20.01.2011
comment
Вы делаете хорошую работу, чтобы это звучало очень прямолинейно; но для меня ваш ответ не совсем проясняет проблему. Может (наверное?) я просто дремучий. Вы говорите: int — это структура, содержащая поле типа int; для меня это звучит кругообразно, как если бы я сказал: «X — это коробка, содержащая X». ). Я понимаю, что тип — это концепция (неудачный выбор слов с моей стороны); что меня смутило, так это то, что в случае с int казалось, что это понятие использовалось для самоопределения... - person Dan Tao; 21.01.2011
comment
... Лично я нашел ответ thecoop чрезвычайно полезным, потому что он заставил меня понять, что объект типа System.Int32 идентичен (в битах) собственному 32-битному целому числу и, следовательно, может рассматриваться как таковой. Таким образом, вместо того, чтобы думать об этом как об аналоге X — это коробка, содержащая X, можно думать, что X — это коробка, содержащая Y, а также физически идентичная Y. Это звучит правильно, или я до сих пор что-то не понимаю? - person Dan Tao; 21.01.2011
comment
@Dan: вокруг данных нет рамки. Я думаю, что идея о том, что структура представляет собой что-то больше, чем ее биты, заключается в том, что вы концептуально ошибались. Смысл типа значения в том, что экземпляр типа значения является значением, точка, не больше, не меньше. У ссылочного типа есть все виды чепухи, окружающие его данные - у него есть поля синхронизации, виртуальные таблицы, дискриминаторы типов и прочее бла-бла-бла. Структура не имеет ничего из этого. Структура int содержит int, и это все. У переменной, которая ссылается на место хранения, могут быть другие данные, но значение — это просто значение. - person Eric Lippert; 21.01.2011
comment
Гах, вскоре после публикации этого комментария я знал, что вы собираетесь исправить мою ошибочную аналогию с коробкой. Я действительно знаю, что экземпляр типа значения является значением и не более того, хотя я не виню вас за то, что вы, вероятно, сомневаетесь во мне в этот момент. Опять же, меня (сначала) смутила его цикличность. Забудьте о коробках; это просто казалось, что сказать Стул есть стул или Красный означает красный. - person Dan Tao; 21.01.2011

Три замечания в дополнение к ответу thecoop:

  1. Ваше утверждение о том, что рекурсивные структуры по своей сути не могут работать, не совсем верно. Это больше похоже на утверждение «это утверждение верно»: что верно, если оно так. Вполне вероятно иметь тип T, единственный член которого имеет тип T: например, такой экземпляр может потреблять 0 байтов (поскольку его единственный член потребляет 0 байтов). Рекурсивные типы значений перестают работать, только если у вас есть второй член (поэтому они запрещены).

  2. Взгляните на определение Mono для Int32. Как видите, на самом деле это это тип, содержащий сам себя (поскольку int — это просто псевдоним для Int32 в C#). Конечно, здесь задействована «черная магия» (т. е. специальный регистр), как поясняют комментарии: среда выполнения будет искать поле по имени и просто ожидать, что оно там — я также предполагаю, что компилятор C# будет использовать особый случай присутствия здесь.

  3. В сборках PE информация о типе представлена ​​​​в виде «больших двоичных объектов подписи типа». Это последовательности объявлений типов, например. для сигнатур методов, но и для полей. Список доступных примитивных типов в такой подписи определен в разделе 22.1.15 спецификации CLR; копия разрешенных значений находится в перечислении CorElementType. По-видимому, API отражения сопоставляет эти примитивные типы с соответствующими типами значений System.XYZ.

person Martin v. Löwis    schedule 20.01.2011
comment
можете ли вы уточнить, что рекурсивные типы значений перестают работать, только если у вас есть второй член? структура T { T m_field; } определенно генерирует ошибку компилятора - person Robert Levy; 20.01.2011
comment
@Robert: правильно, это неправильно (на C #). Тем не менее, нет внутренней причины для рекурсивных типов значений с неправильным форматом одного члена - было бы правдоподобно (но бесполезно) разрешить конструкцию. Это перестает быть правдоподобным, если у вас есть второй элемент данных (поскольку тогда он будет занимать бесконечный объем памяти), поэтому он запрещен. - person Martin v. Löwis; 20.01.2011