В отличие от такого высокоуровневого языка, как C#, язык CIL не просто определяет свой собственный набор ключевых слов. Набор лексем, понятных компилятору CIL, разделяется на три большие категории, в зависимости от семантического подтекста:
• директивы CIL;
• атрибуты CIL;
• коды операций CIL.
Каждая категория лексем CIL выражается с помощью своих специальных синтаксических конструкций, а сами лексемы объединяются с тем, чтобы в результате получился работоспособный компоновочный блок .NET.
Прежде всего, есть множество известных лексем CIL, которые используются для описания полной структуры компоновочного блока .NET. Эти лексемы называются директивами. Директивы CIL используются дли информирования компилятора CIL о том, как определять пространства имен, типы и члены, содержащиеся в компоновочном блоке.
Синтаксически директивы обозначаются с помощью префикса, представленного точкой (.) (например, .namespace, .class, .publickeytoken, .override, .method, .assembly и т.д.). Так, если ваш файл *.il (обычное расширение для файла, содержащего программный код CIL) имеет одну директиву .namespace и три директивы .сlass, компилятор CIL сгенерирует компоновочный блок, который определит одно пространства имен . NET и три типа класса .NET.
Во многих случаях директивы CIL сами по себе оказываются недостаточно информативными, чтобы дать исчерпывающее определение соответствующего типа .NET или его члена. Поэтому многие директивы CIL сопровождаются различными атрибутами CIL, сообщающими о том, как должна обрабатываться данная директива. Например, директива .class может сопровождаться атрибутам public (чтобы задать параметры видимости типа), атрибутом extends (чтобы явно указать базовый класс типа) или атрибутом implements (чтобы задать список интерфейсов, поддерживаемых типом).
После определения компоновочного блока .NET, пространства имен и набора типов в терминах GIL с использованием различных директив и связанных атрибутов остается одно – предложить программную логику реализации типа. Это является задачей кодов операций. В соответствии с традициями других языков низкого уровня, коды операций CIL, как правило, имеют просто непроизносимые аббревиатуры. Например, чтобы определить переменную строки, используется не понятный код операции LoadString, a ldstr.
Но все же, что не может не радовать, некоторые коды операций CIL в точности соответствуют их аналогам в C# (это, например, box, unbox, throw и sizeof). Вы сможете убедиться в том, что коды операций CIL всегда используются в контексте реализации члена и, в отличие от директив CIL, они никогда не обозначаются префиксом, заданным точкой.
Различия между мнемоникой и кодом операции CIL
Как только что объяснялось, коды операций, например ldstr, используются для реализации членов данного типа. Но в реальности лексемы (в том числе и ldstr) являются мнемониками CIL, представляющими на самом деле двоичные коды операций CIL. Чтобы пояснить различие, предположим, что у нас есть следующий метод, созданный средствами C#.
static int Add(int x, int у) {
return х + у;
}
В терминах CIL сложение двух чисел представлено кодом операции 0X58. Аналогично для представления вычитания используется код операции 0X59, а действие, соответствующее размещению нового объекта в управляемой динамической памяти, обозначается кодом операции 0X73. С учетом сказанного должно быть ясно, что CIL-код, обрабатываемый JIT-компилятором, на самом деле является набором двоичных данных.
К счастью, для каждого двоичного кода операции CIL есть соответствующая мнемоника. Например, мнемоника add может использоваться вместо 0X58, sub – вместо 0X59, a newobj – вместо 0X73. Ввиду указанных различий между мнемониками и кодами операций, нетрудно догадаться, что декомпиляторы CIL, такие как, например, ildasm.exe, переводят двоичные коды операций компоновочного блока в соответствующую мнемонику CIL.
.method public hidebysig static int32 Add(int32 x, int32 y) cil managed {
…
// Лексема 'add' является более понятной мнемоникой CIL,
// используемой для представления кода операции 0X58.
add
…
}
Тем, кто не сталкивается с необходимостью разработки низкоуровневого программного обеспечения .NET (например, пользовательского управляемого компилятора), обычно не приходится иметь дело непосредственно с числовыми кодами операций CIL. Поэтому практически всегда, когда программисты .NET говорят о "кодах операций CIL", они (как и я в этом тексте) имеют в виду набор более понятной мнемоники, а не лежащие в ее основе двоичные значения.
Читать дальше