Как «примитивные» типы определяются нерекурсивно? - PullRequest
19 голосов
/ 20 января 2011

Поскольку 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 , мне стало гораздо понятнее.

Ответы [ 3 ]

11 голосов
/ 20 января 2011

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

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

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

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

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

7 голосов
/ 21 января 2011

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

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

Кажется, нам здесь лгут

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

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

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

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

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

5 голосов
/ 20 января 2011

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

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

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

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

...