Разница между отмеченными и не отмеченными здесь на самом деле является немного ошибкой в IL или просто некорректным исходным кодом (я не эксперт по языку, поэтому я не буду комментировать, если компилятор C# генерирует правильные IL для двусмысленного исходного кода). Я скомпилировал этот тестовый код, используя версию компилятора C# 4.0.30319.1 (хотя версия 2.0, похоже, делала то же самое). Параметры командной строки, которые я использовал, были:/o +/unsafe/debug: pdbonly.
Для незарегистрированного блока, мы имеем этот IL-код:
//000008: unchecked
//000009: {
//000010: Console.WriteLine("{0:x}", (long)(testPtr + offset));
IL_000a: ldstr "{0:x}"
IL_000f: ldloc.0
IL_0010: ldloc.1
IL_0011: add
IL_0012: conv.u8
IL_0013: box [mscorlib]System.Int64
IL_0018: call void [mscorlib]System.Console::WriteLine(string,
object)
В IL компенсировано 11, адд получает 2 операндами, один из типа байт *, а другой тип uint32. По спецификации CLI они действительно нормализуются в собственные int и int32 соответственно. Согласно спецификации CLI (раздел III, если быть точным), результатом будет собственный int. Таким образом, операнд secodn должен быть повышен до типа int int. Согласно спецификации, это достигается посредством расширения знака. Таким образом, uint.MaxValue (который равен 0xFFFFFFFF или -1 в подписанной нотации), является расширением знака до 0xFFFFFFFFFFFFFFFF. Затем добавляются 2 операнда (0x0000000008000000L + (-1L) = 0x0000000007FFFFFFL). Командный код conv необходим только для целей проверки, чтобы преобразовать собственный int в int64, который в сгенерированном коде является nop.
Теперь для проверяемого блока, мы имеем этот IL:
//000012: checked
//000013: {
//000014: Console.WriteLine("{0:x}", (long)(testPtr + offset));
IL_001d: ldstr "{0:x}"
IL_0022: ldloc.0
IL_0023: ldloc.1
IL_0024: add.ovf.un
IL_0025: conv.ovf.i8.un
IL_0026: box [mscorlib]System.Int64
IL_002b: call void [mscorlib]System.Console::WriteLine(string,
object)
Это практически идентично, за исключением добавления и ко опкода кроме. Для добавления кода операции мы добавили 2 'суффикса'. Первый - суффикс «.ovf», который имеет очевидное значение: проверьте переполнение, но также необходимо «включить второй суффикс:» .un ». (т. е. нет «add.un», только «add.ovf.un»). «.un» имеет 2 эффекта.Наиболее очевидным является то, что дополнительная проверка переполнения выполняется так, как если бы операнды были целыми без знака. Из наших классов CS обратный путь назад, когда, надеюсь, мы все помним, что благодаря двоичному кодированию дополнений двух, добавление дополнений и без знака одно и то же, поэтому «.un» действительно влияет только на проверку переполнения, правильно?
Неправильно.
Помните, что в стеке IL у нас нет 2 64-битных чисел, у нас есть int32 и собственный int (после нормализации). Ну, «.un» означает, что преобразование из int32 в native рассматривается как «conv.u», а не по умолчанию «conv.i», как указано выше. Таким образом, uint.MaxValue равен нулю, равному 0x00000000FFFFFFFFL. Затем добавление правильно создает 0x0000000107FFFFFFL. Код conv convec гарантирует, что неподписанный операнд может быть представлен как подписанный int64 (который он может).
Ваши исправления работают только для 64-битного. На уровне IL более правильным решением было бы явно преобразовать операнд uint32 в собственный int или неподписанный собственный int, а затем и check, и unchecked будут одинаковыми для 32-разрядных и 64-разрядных.
Вы можете показать нам некоторые из вашего кода? – aL3891
Смещение подписано и указатель без знака? Вам редко нужно отрицательное смещение. –
Соответствующие части кода выглядят следующим образом: this.currentLocation + = sizeof (byte), this.currentLocation + = numberOfChildren * sizeof (ushort), this.currentLocation + = subTree.Length и т. Д. Никаких отрицательных смещений. –