2015-06-11 4 views
4

Я искал какой-то старый, рефлекторный декомпилированный исходный код, который я выкопал. DLL изначально была скомпилирована из источника Visual Basic .NET, используя .NET 2.0 - кроме этого у меня больше нет информации о компиляторе.Странный код IL, испущенный некоторым компилятором

В какой-то момент произошло что-то странное. В коде не было ответвления, хотя условие должно было быть сохранено. Чтобы быть точным, это был филиал:

[...] 
if (item.Found > 0) 
{ 
    [...] 

Теперь самое интересное было то, что если item.Found был -1, был введен объем if заявления. Тип item.Found был int.

Чтобы выяснить, что происходит, я пошел искать в коде IL и нашел это:

ldloc.3 
ldfld int32 Info::Found 
ldc.i4.0 
cgt.un 
stloc.s flag3 
ldloc.s flag3 
brfalse.s L_0024 

Очевидно Отражатель был здесь не так. Правильный декомпилированный код должен быть:

if ((uint)item.Found > (uint)0) 
{ ... } 

ОК до настоящего времени для контекста. Теперь для моего вопроса.

Во-первых, я не могу представить, чтобы кто-то действительно писал этот код; ИМО никто с разумным разумом делает различие между «-1» и «0» таким образом - это единственные два значения, которые «Найдено» могут иметь.

Итак, это оставляет мне вывод, что компилятор делает то, что я не понимаю.

  • Почему на земле/в каком контексте компилятор генерирует IL-код, подобный этому? Какая польза от этой проверки (вместо ceq или bne_un - это то, что я ожидал бы и обычно генерируется C#)?
  • И родственный: какой был исходный исходный код, скорее всего?
+0

Вы уверены, что тип item.Found был ИНТ и не UINT в исходном коде? Просто дважды проверяйте, так как для меня непонятно, есть ли у вас доступ к исходному источнику или нет. –

+1

Почти наверняка исходным кодом был эквивалент VB 'if (item.Found)' where' Found' был преобразован из Boolean. Вы просите рефлектора показать код VB, как если бы это был код C#, который, конечно же, может вызвать странные ситуации. Что вы получите, если указате рефлектору, чтобы показать его как код VB? (Помните, что VB представляет собой Boolean как 0 или -1 при преобразовании в числовые типы) –

+0

@MatthewWatson Да, я пробовал - если я покажу его как VB.Net, ничего не изменится. Тем не менее, хотя Reflector делает приличную работу с C#, это не особенно хорошо с такими конструкциями. Тем не менее, то, что вы говорите, интересно - зачем вы компилируете это как 'cgt.un'? – atlaste

ответ

3

В качестве эксперимента я составил этот VB код:

Dim test As Boolean 
test = True 
Dim x As Integer 
x = test 
If x Then Console.WriteLine("True") 

ИЛ для версии этого является:

.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() 
.entrypoint 
.maxstack 2 
.locals init (
    [0] bool test, 
    [1] int32 x) 
L_0000: ldc.i4.1 
L_0001: stloc.0 
L_0002: ldloc.0 
L_0003: ldc.i4.0 
L_0004: cgt.un 
L_0006: neg 
L_0007: stloc.1 
L_0008: ldloc.1 
L_0009: ldc.i4.0 
L_000a: cgt.un 
L_000c: brfalse.s L_0018 
L_000e: ldstr "True" 
L_0013: call void [mscorlib]System.Console::WriteLine(string) 
L_0018: ret 

Обратите внимание на использование cgt.un

интерпретации рефлектора как C#:

bool test = true; 
int x = (int) -(test > false); 
if (x > 0x0) 
{ 
    Console.WriteLine("True"); 
} 

И как VB:

Dim test As Boolean = True 
Dim x As Integer = CInt(-(test > False)) 
If (x > &H0) Then 
    Console.WriteLine("True") 
End If 

Поэтому я вывод, сгенерированный код связан с преобразованием VB Boolean в числовое значение.

+0

* ough *. Хорошо, это отвечает на вторую часть моего вопроса. Спасибо за это! Мне все еще очень интересно узнать о первой части: почему команда компилятора решила, что это хорошая идея, чтобы скомпилировать ее так. Я имею в виду: ceq_un и ceq чувствуют себя как (a) более сильное ограничение и (б), как будто они предназначены для этого. :-) – atlaste

+0

@atlaste Я думаю, только команда компилятора будет знать ответ на этот вопрос! –

+0

Ну, я все равно предлагаю;) Тем временем обратите внимание, что 'ceq_un' не существует; подписанное и неподписанное равенство одинаковы, поэтому нет необходимости в 'ceq_un'. –

4

Выглядит изворотливым, но это связано с предыдущими версиями Visual Basic, поколение, которое закончилось с VB6. У него было совсем другое представление типа Boolean, a VARIANT_BOOL. Это все еще является фактором в VB.NET из-за необходимости поддерживать устаревший код.

Представление значения для True было другим, оно было -1.False - 0, как в .NET.

В то время как это выглядит очень изворотливым выбором, любой другой язык использует 1 для представления True, есть очень. В нем исчезают различия между логическими и математическими операциями And и Or. Что довольно приятно, еще одна вещь, которую программисту не нужно изучать. То, что это препятствие для обучения, довольно очевидно из кода, который любой программист C# пишет, они слепо применяют && или || в своих операторах if(). Даже если это не очень хорошая идея, эти операторы дороги из-за требуемой короткозамкнутой ветви в машинный код. Если левый операнд плохо предсказан прогнозом ветвления процессора, тогда вы легко потеряете кучу циклов процессора из-за конвейера.

Приятный, но не без проблем, And и Or всегда оценивают как левый, так и правый операнды. И у этого есть умение для исключений отключения, иногда вам действительно нужно короткое замыкание. VB.NET добавила операторы AndAlso и OrElse, чтобы устранить эту проблему.

Таким образом, cgt.un имеет смысл, который может обрабатывать как значение буфера .NET , так и устаревшее значение. Неважно, истинное значение равно -1 или 1. И не волнует, что переменная или выражение фактически являются логическими, разрешенными в VB.NET с опцией Strict Off.

+0

И в этом отношении, как 'VARIANT_BOOL', так и .NET' Boolean' должны обрабатывать любое значение, которое не равно нулю, как истинное (хотя некоторые части .NET делают предположение, что это никогда не произойдет с 'Boolean'). 'cgt.un' тоже поймает всех. –

+0

С уважением, я не думаю, было ли это «VARIANT_BOOL» в происхождении или нет, релевантно, все, что имеет значение, касается ли он нулевого или ненулевого. –

+0

@ ХансПасант Как всегда, спасибо Хансу, высоко оценил; здесь немного деталей VB.Net, о которых я не знал. PS (просто дополнительная информация для тех, кто интересуется): ленивая оценка иногда может быть быстрее, чем нелатическая оценка в этих случаях, то есть: если скачок можно предсказать правильно и т. Д., См. Также: http: //oai.cwi. nl/oai/asset/21351/21351B.pdf для экспериментов в движке базы данных. – atlaste

1

Давайте сначала рассмотрим, что есть, как вы говорите, два возможных значения -1 и 0. Есть вопрос, что следует делать, если 42 попадает туда; возможно ли это (вы правы в своем заявлении) или просто о возможном (значение действует как вариант_bool, в котором -1 является нормальным истинным значением, но все ненулевые должны рассматриваться как истинные), это стоит рассмотреть в любом случае. И имеет смысл лечить 42 так же, как мы лечим -1; то есть имеет смысл рассматривать все ненулевые как одно и то же.

И даже если нет абсолютно никакого другого возможного ненулевого значения, чем -1, он по-прежнему обобщает на «test is non-zero», который является очень распространенным случаем в другом месте, поэтому имеет смысл считать, что «тест ненулевой "случай. Это особенно важно, если компилятор не знает, что -1 - единственное возможное ненулевое значение (очень вероятно).

Теперь возникает вопрос о том, нужно ли напрямую связываться с значением (с brfalse, brtrue и т. Д.) Или выполнить логическую операцию, а затем развернуть результат. Как правило, как C# и VB.NET компиляторов будет производить логическое значение, а затем разветвляются на том, что в отладочной версии:

Простой код:

public void TestBool(bool x) 
{ 
    if(x) 
    throw new ArgumentOutOfRangeException(); 
} 

Debug CIL:

nop 
    ldarg.1 
    ldc.i4.0 
    ceq 
    stloc.0 
    ldloc.0 
    brtrue.s NoError 
    newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor() 
    throw 
NoError: 
    ret 

релиз CIL :

ldarg.1 
    brfalse.s NoError 
    newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor() 
    throw 
NoError: 
    ret 

Дополнительные шаги по существу делают x == true до doi ng отладка ветвящихся средств. Подобные эффекты иногда встречаются в коде выпуска, хотя и реже.

Итак, по этой причине мы проводим сравнение перед веткой в ​​вашем коде, а не только с веткой.

Теперь возникает другой вопрос: следует ли нам проверять, что значение равно нулю или проверить, что значение не равно нулю; либо эквивалентно много, как:

if(x) 
    DoSomething(); 

И

if(!x) 
{ 
} 
else 
    DoSomething(); 

эквивалентны.

По этой причине ceq может быть использован, с последующим разветвлением, подходящим для случая, когда item.Found как 0. Но это, если что-то более разумно использовать cne с последующим разветвлением, подходящим для случая, когда item.Found не является 0.

Но нет такой инструкции CIL, как cne, или что-либо, что сравнимо проверяет, если что-то не равно. Обычно для выполнения «проверки не равно» мы делаем последовательность ceq, ldc.i4.0, ceq; проверьте, что два значения равны, а затем проверьте, что результат этой проверки является ложным.

К счастью, в общем случае, что мы проверяем, что-то не равно это 0 нам не нужен, потому что cnecgt.un логически эквивалентен в этом случае гипотетического cne. Это делает cgt.un очевидным выбором, если мы хотим проверить, что что-то не равно нулю.

И, следовательно, в то время как IYO «никто с разумным разумом делает различие между« -1 »и« 0 »таким образом,« это очень разумный способ действительно проверить ненулевое значение вообще. И действительно, cgt.un часто появляется как просто такой ненулевой тест.

И связанный с этим: какой исходный исходный код наиболее вероятно?

If item.Found Then 
    'More stuff 
End If 

Что эквивалентно C#

if(item.Found != 0) 
{ 
    //More stuff 
} 
+0

Я вижу, что @xanatos имел правильный ответ, если не полную оправдательную логику, и тогда был убежден, что они этого не сделали. –

+0

Хорошее объяснение, спасибо за это. Возможно, это просто моя голова, которая путается: в последнее время я много читаю в компиляторах SSA, и одним из общих шагов является определение переменных границ. Это полезно для многих других вещей, таких как устранение пограничных проверок и т. Д. Теперь, если у вас есть операция равенства, довольно легко определить границы источника, которые имеют значение, тем самым уменьшая сложность - если у вас есть такая конструкция, она чувствует себя более сложно. Подумав об этом еще (точнее: 0 - const), я не уверен, имеет ли это значение вообще. – atlaste

+0

Ну, кое-что для этого: 1. Является ли ваше определение границы действительным (это 100%, доказуемо и явно ясно, что значение может быть только 0 или -1)? 2. Компилятор VB.NET делает такую ​​оптимизацию (в целом компиляторы VB.NET и C# делают очень мало оптимизации, оставляя большую часть этого дрожания, и это было из версии с 9 лет назад). 3. Помогла бы пограничная проверка. Eh, устраняя граничные проверки, давайте сократим код, но что бы мы сократили? В самом деле, это может быть где-то, если мы вводим неподписанные сравнения в соответствии с http://stackoverflow.com/a/29348411/400547 –

Смежные вопросы