Resposta curta:
Como não há instrução "compare-not-equal" em IL, o !=
operador C # não tem correspondência exata e não pode ser traduzido literalmente.
Existe, no entanto, uma instrução "compare-equal" ( ceq
uma correspondência direta com o ==
operador), portanto, no caso geral, x != y
é traduzida como seu equivalente um pouco mais longo (x == y) == false
.
Há também uma instrução "compare-than-than" em IL ( cgt
), que permite ao compilador pegar certos atalhos (ou seja, gerar código IL mais curto), sendo que comparações de desigualdade de objetos com nulos obj != null
são traduzidas como se fossem " obj > null
"
Vamos entrar em mais alguns detalhes.
Se não houver instrução "compare-not-equal" em IL, como o método a seguir será traduzido pelo compilador?
static bool IsNotEqual(int x, int y)
{
return x != y;
}
Como já foi dito acima, o compilador transformará o arquivo x != y
em (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
}
Acontece que o compilador nem sempre produz esse padrão bastante longo. Vamos ver o que acontece quando substituímos y
pela constante 0:
static bool IsNotZero(int x)
{
return x != 0;
}
A IL produzida é um pouco menor do que no caso geral:
.method private hidebysig static bool IsNotZero(int32 x) cil managed
{
ldarg.0 // x
ldc.i4.0 // 0
cgt.un // (note: just one comparison)
ret
}
O compilador pode tirar proveito do fato de que números inteiros assinados são armazenados no complemento de dois (onde, se os padrões de bits resultantes são interpretados como números inteiros não assinados - é isso que .un
significa - 0 tem o menor valor possível), portanto é traduzido x == 0
como se fosse unchecked((uint)x) > 0
.
Acontece que o compilador pode fazer o mesmo para verificações de desigualdade contra null
:
static bool IsNotNull(object obj)
{
return obj != null;
}
O compilador produz quase o mesmo IL que para IsNotZero
:
.method private hidebysig static bool IsNotNull(object obj) cil managed
{
ldarg.0
ldnull // (note: this is the only difference)
cgt.un
ret
}
Aparentemente, o compilador pode assumir que o padrão de bits da null
referência é o menor padrão de bits possível para qualquer referência de objeto.
Este atalho é mencionado explicitamente no Common Language Infrastructure Annotated Standard (1ª edição de outubro de 2003) (na página 491, como nota de rodapé da Tabela 6-4, "Comparações binárias ou operações de filial"):
" cgt.un
é permitido e verificável em ObjectRefs (O). Isso é comumente usado ao comparar um ObjectRef com null (não há instruções" compare-not-equal ", que de outra forma seriam uma solução mais óbvia)."
int
intervalo tenham a mesma representaçãoint
que elesuint
. Esse é um requisito muito mais fraco que o complemento de dois.int
já foram assumidas pelo mesmo valor emuint
, portanto, todas as representações correspondentes aos valores negativos deint
devem corresponder a algum valoruint
maior que0x7FFFFFFF
, mas não importa realmente qual valor esse é. (Na verdade, tudo o que é realmente necessário é que o zero é representado da mesma maneira em ambosint
euint
.)cgt.un
tratar umint
como umuint
sem alterar o padrão de bits subjacente. (Imagine quecgt.un
seria primeiro tentar underflows correção mapeando todos os números negativos a 0. Nesse caso, você, obviamente, não poderia substituir> 0
a!= 0
.)>
seja IL verificável. Dessa forma, é possível comparar dois objetos não nulos e obter um resultado booleano (que não é determinístico). Esse não é um problema de segurança de memória, mas parece um design impuro que não faz parte do espírito geral do código gerenciado com segurança. Esse design vaza o fato de que as referências a objetos são implementadas como ponteiros. Parece uma falha de design da CLI do .NET.ldnull
,initobj
, enewobj
). Portanto, o uso decgt.un
para comparar referências de objetos com referência nula parece contradizer a seção III.1.1.4 de mais de uma maneira.