Вчера я нашел это странное поведение в своем коде С#:
Stack<long> s = new Stack<long>();
s.Push(1); // stack contains [1]
s.Push(2); // stack contains [1|2]
s.Push(3); // stack contains [1|2|3]
s.Push(s.Pop() * 0); // stack should contain [1|2|0]
Console.WriteLine(string.Join("|", s.Reverse()));
Я предположил, что программа напечатает 1|2|0
, но на самом деле она напечатала 1|2|3|0
.
Глядя на сгенерированный код IL (через ILSpy), вы можете видеть, что s.Pop() * 0
оптимизирован просто 0
:
// ...
IL_0022: ldloc.0
IL_0023: ldc.i4.0
IL_0024: conv.i8
IL_0025: callvirt instance void class [System]System.Collections.Generic.Stack`1<int64>::Push(!0)
// ...
Декомпиляция ILSpy:
Stack<long> s = new Stack<long>();
s.Push(1L);
s.Push(2L);
s.Push(3L);
s.Push(0L); // <- the offending line
Console.WriteLine(string.Join<long>("|", s.Reverse<long>()));
Сначала я тестировал это в Windows 7 с обновлением Visual Studio 2015 Update 3 с режимом Release (/optimize
) и Debug mode и с различными целевыми фреймворками (4.0, 4.5, 4.6 и 4.6.1). Во всех 8 случаях результат был таким же (1|2|3|0
).
Затем я тестировал его под Windows 7 с обновлением Visual Studio 2013 Update 5 (опять же со всеми комбинациями режима Release/Debug и целевой структуры). К моему удивлению, утверждение здесь не оптимизировано и дает ожидаемый результат 1|2|0
.
Поэтому я могу заключить, что это поведение не зависит от /optimize
и от флага целевой инфраструктуры, а от используемой версии компилятора.
Из интереса я написал аналогичный код на С++ и скомпилировал его с текущей версией gcc. Здесь вызов функции, умноженный на ноль, не оптимизирован, и функция выполнена правильно.
Я думаю, что такая оптимизация была бы действительна только в том случае, если stack.Pop()
была чистой функцией (которой она определенно не является). Но я не решаюсь назвать это ошибкой, я предполагаю, что это просто неизвестная мне функция?
Является ли эта "функция" документирована где-нибудь и есть (простой) способ отключить эту оптимизацию?