Коды операций CIL
95C# --- Сборки .NET --- Коды операций CIL
Последний аспект CIL-кода связан с ролью, которую играют различные коды операций. Вспомните, что под кодом операции понимается лексема CIL, используемая для построения логики реализации члена. Все поддерживаемые в CIL коды операций (которых немало) могут быть разделены на три основных категории:
коды операций, позволяющие управлять выполнением программы;
коды операций, позволяющие вычислять выражения;
коды операций, позволяющие получать доступ к значениям в памяти (через параметры, локальные переменные и т.д.).
В таблице приведены некоторые наиболее полезные коды операций, имеющие непосредственное отношение к логике реализации членов (для удобства они сгруппированы по функциональности):
Коды операций | Описание |
add, sub, mul, div, rem | Позволяют выполнять сложение, вычитание, умножение и деление двух значений (rem возвращает остаток от деления) |
and, or, not, xor | Позволяют выполнять соответствующие побитовые операции над двумя значениями |
ceq, cgt, clt | Позволяют сравнивать два значения в стеке различными способами: ceq — сравнение на предмет равенства; cgt — сравнение на предмет того, является ли одно из них больше другого; clt — сравнение на предмет того, является ли одно из них меньше другого |
box, unbox | Применяются для преобразования ссылочных типов в типы значения и наоборот |
ret | Применяется для выхода из метода и (если необходимо) возврата значения вызывающему коду |
beq, bgt, ble, bit, switch | Применяются (вместе с другими похожими кодами операций) для управления логикой ветвления внутри метода: beq — позволяет переходить к определенной метке в коде, если при проверке значения оказываются равными; bgt — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается больше другого; ble — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается меньше или равным другому; bit — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается меньше другого. Все связанные с ветвлением коды операций требуют указания в CIL-коде метки, к которой должен осуществляться переход в случае, если результат проверки оказывается истинным |
call | Применяется для вызова члена определенного типа |
newarr, newobj | Позволяют размещать в памяти, соответственно, новый массив или новый объект |
Коды операций следующей обширной категории применяются для загрузки (заталкивания) аргументов в виртуальный стек выполнения. Обратите внимание, что все эти ориентированные на выполнение загрузки коды операций сопровождаются префиксом ld (который означает "load" — загрузка):
Код операций | Описание |
ldarg | Позволяет загружать в стек аргумент метода. Помимо основного варианта ldarg (который работает с индексом, представляющим аргумент), существует множество других вариантов. Например, есть варианты ldarg, которые работают с числовым суффиксом (ldarg_0) и позволяют жестко кодировать загружаемый аргумент. Также есть варианты, позволяющие жестко кодировать как только один тип данных, к которому относится аргумент, с помощью константной нотации CIL (например, ldarg_I4 для int32), так и тип данных и значение вместе (например, ldarg_I4_5, позволяющий загружать int32 со значением 5) |
ldc | Позволяет загружать в стек значение константы |
ldfld | Позволяет загружать в стек значение поля уровня экземпляра |
ldloc | Позволяет загружать в стек значение локальной переменной |
ldobj | Позволяет получать все значения размещаемого в куче объекта и помещать их в стек |
ldstr | Позволяет загружать в стек строковое значение |
Помимо кодов операций, связанных с загрузкой, в CIL поддерживаются коды операций, которые позволяют явным образом извлекать из стека самое верхнее значение. Как уже было показано в нескольких примерах ранее, извлечение значения из стека обычно подразумевает его сохранение во временном локальном хранилище с целью дальнейшего использования (например, параметра для последующего вызова метода). Из-за этого многие коды операций, которые позволяют извлекать текущее значение из виртуального стека выполнения, сопровождаются префиксом st (от "store" — сохранить); ниже перечислены некоторые наиболее часто используемые из них:
Код операций | Описание |
pop | Позволяет удалять значение, которое в текущий момент находится на верхушке стека вычислений, но не сохранять его |
starg | Позволяет сохранять самое верхнее значение из стека в аргументе метода с определенным индексом |
stloc | Позволяет извлекать текущее значение из верхушки стека вычислений и сохранять его в списке локальных переменных с определенным индексом |
stobj | Позволяет копировать значение определенного типа из стека вычислений в память по определенному адресу |
stsfld | Позволяет заменять значение статического поля значением из стека вычислений |
Следует принимать во внимание, что различные коды операций в CIL могут предусматривать неявное извлечение значений из стека во время решения поставленной перед ними задачи. Например, при вычитании одного числа из другого с использованием кода операции sub должно быть очевидным, что перед собственно вычислением sub должна извлечь из стека два следующих доступных значения. Результат вычисления будет снова помещен в стек.
Директива .maxstack
При написании кода реализации методов непосредственно в CIL необходимо помнить об одной особой директиве, которая называется .maxstack. Эта директива позволяет указать максимальное количество переменных, которое может помещаться в стек в любой момент во время выполнения метода. Директива имеет значение по умолчанию 8, подходящее для подавляющего большинства создаваемых методов. При желании можно вручную вычислять количество локальных переменных в стеке и указывать его явно.