Короткий ответ:
В IL нет инструкции «сравнить-не-равно», поэтому !=
оператор C # не имеет точного соответствия и не может быть переведен буквально.
Тем не менее, существует команда «сравнить с равным» ( ceq
прямое соответствие ==
оператору), поэтому в общем случае она x != y
переводится как ее немного более длинный эквивалент (x == y) == false
.
В IL ( ) есть также инструкция «сравнить больше, чем», cgt
которая позволяет компилятору использовать определенные комбинации клавиш (т. Е. Генерировать более короткий код IL), при этом неравенство сравнения объектов с нулем obj != null
переводится так, как если бы они были » obj > null
».
Давайте углубимся в некоторые детали.
Если в IL нет инструкции «сравнить-не-равно», то как следующий метод будет переведен компилятором?
static bool IsNotEqual(int x, int y)
{
return x != y;
}
Как уже говорилось выше, компилятор превратит x != y
в (x == y) == false
:
.method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed
{
ldarg.0 // x
ldarg.1 // y
ceq
ldc.i4.0 // false
ceq // (note: two comparisons in total)
ret
}
Оказывается, компилятор не всегда создает этот довольно длинный шаблон. Посмотрим, что произойдет, когда мы заменим y
на константу 0:
static bool IsNotZero(int x)
{
return x != 0;
}
Производимый ИЛ несколько короче, чем в общем случае:
.method private hidebysig static bool IsNotZero(int32 x) cil managed
{
ldarg.0 // x
ldc.i4.0 // 0
cgt.un // (note: just one comparison)
ret
}
Компилятор может воспользоваться тем фактом, что целые числа со знаком хранятся в дополнении к двум (где, если результирующие битовые шаблоны интерпретируются как целые числа без знака - это то, что .un
значит - 0 имеет наименьшее возможное значение), поэтому он переводится так, x == 0
как если бы он был unchecked((uint)x) > 0
,
Оказывается, компилятор может сделать то же самое для проверки неравенства null
:
static bool IsNotNull(object obj)
{
return obj != null;
}
Компилятор выдает почти тот же IL, что и для IsNotZero
:
.method private hidebysig static bool IsNotNull(object obj) cil managed
{
ldarg.0
ldnull // (note: this is the only difference)
cgt.un
ret
}
По-видимому, компилятору разрешается предполагать, что битовая комбинация null
ссылки является наименьшей битовой комбинацией, возможной для любой ссылки на объект.
Этот ярлык явно упоминается в Аннотированном стандарте общеязыковой инфраструктуры (1-е издание от октября 2003 г.) (на стр. 491, в качестве сноски к Таблице 6-4, «Двоичные сравнения или операции в филиалах»):
« cgt.un
разрешено и проверяемо в ObjectRefs (O). Это обычно используется при сравнении ObjectRef с нулевым значением (нет инструкции« сравнить-не-равно », которая в противном случае была бы более очевидным решением).»
int
диапазоне имеют такое же представление,int
как и вuint
. Это гораздо более слабое требование, чем дополнение к двум.int
, уже заняты одним и тем же значением вuint
, поэтому все представления, соответствующие отрицательным значениям,int
должны соответствовать некоторому значениюuint
больше, чем0x7FFFFFFF
, но на самом деле не имеет значения, какое значение этого является. ( На самом деле, все , что действительно требуется, что ноль представляется одинаково в обоихint
иuint
.)cgt.un
обрабатываетint
какuint
без изменения базовый битовый шаблон. (Представьте себе , чтоcgt.un
бы сначала попытаться исправить потери значимости путем сопоставления всех отрицательных чисел в 0. В этом случае вы , очевидно , не может заменить> 0
для!= 0
.)>
проверяемый IL. Таким образом, можно сравнить два ненулевых объекта и получить логический результат (который не является детерминированным). Это не проблема безопасности памяти, но похоже на нечистую конструкцию, которая не соответствует общему духу безопасного управляемого кода. В этом проекте прослеживается тот факт, что ссылки на объекты реализованы в виде указателей. Похоже, недостаток дизайна .NET CLI.ldnull
,initobj
иnewobj
). Таким образом, использованиеcgt.un
сравнения объектов с нулевой ссылкой противоречит разделу III.1.1.4 более чем одним способом.