Результат добавления int32 в 64-битный собственный int?
При добавлении int32
в 64-разрядный native int
, является ли знак CLR расширением или нулевым расширением 32-разрядного целого? И самое главное: на основе какой информации он делает этот выбор?
Я пишу компилятор .NET и прочитал спецификацию ECMA, но не смог найти ответ.
CLI поддерживает только подмножество этих типов в своих операциях над значениями, хранящимися в его стеке оценки: int32
, int64
и native int
.
- ECMA 335, Раздел я 12.1: Поддерживаемые типы данных
Так как значения в стеке оценки не имеют информации об их подписи, инструкции, для которых важна подпись операндов, имеют два варианта: один для подписанных и один для целых чисел без знака. Команды add
, sub
и mul
(те, которые не проверяют переполнение), не должны заботиться о подписности операндов, если операнды имеют одинаковый размер и, следовательно, имеют только единый вариант. Однако операнды не всегда одинакового размера...
ECMA 335, раздел III 1.5: таблица типов операндов утверждает, что int32
и native int
можно добавлять, вычитать, умножать и разделены. В результате снова будет native int
. В 64-битной системе a native int
имеет ширину 64 бит.
ldc.i4.0 // Load int32 0
conv.i // Convert to (64-bit) native int
ldc.i4.m1 // Load int32 -1
add // Add native int 0 and int32 0xFFFFFFFF together
Итак, каков будет результат здесь? Обратите внимание, что в соответствии со спецификацией время выполнения не требует отслеживания точных типов или подписи значений в стеке: он знает только int32
, int64
и native int
(и некоторые другие, которые не актуальны здесь).
Я бы предположил, что арифметика IntPtr
и UIntPtr
, так как она внутренне представлена как native ints, также будет использовать этот вид добавления. Однако ILSpy показывает, что добавление IntPtr
и int32
в С# вызывает перегруженный + оператор класса IntPtr
, который принимает только подписанный аргумент int32
.
Выполнение непосредственно в CIL (с помощью команды add
) также указывает, что целое число интерпретируется как подписанное. Он также должен был быть реализован в Mono, но я не мог найти ссылок на мои результаты.
Ответы
Ответ 1
Подпись не имеет значения при добавлении двух значений одного и того же битрейта. Например, добавление 32-бит -10 (0xfffffff6
) в 32-разрядный 10 (0x0000000a
) будет корректно давать 0. Из-за этого в CIL (Common Language Language) есть только одна инструкция add
.
Однако при добавлении двух значений разных битов, значение подписи имеет значение. Например, добавление 32-разрядных от -10 до 64-разрядных 10 может привести к 4294967296 (0x100000000
), когда будет выполнено без знака, и 0 при подписании.
Инструкция CIL add
позволяет добавлять собственное целое число и 32-разрядное целое число. Нативное целое число может быть 64-битным (в 64-битной системе). Тестирование показывает, что add
рассматривает 32-битное целое число как целое число со знаком и расширяет его. Это не всегда правильно и может считаться ошибка. В настоящее время Microsoft не собирается его исправлять.
Поскольку проверка переполнения зависит от того, рассматриваются ли операнды как неподписанные или подписанные, существуют два варианта add.ovf
: add.ovf
(подпись) и add.ovf.un
(без знака). Тем не менее, эти варианты также правильно подписывают-удлиняют нуль-расширяют меньший операнд при добавлении 32-разрядного целого к наследуемому целому числу.
Таким образом, добавление собственного целого числа и 32-битного целого числа без знака может давать разные результаты в зависимости от параметра проверки переполнения С#. По-видимому, тот факт, что я не мог понять это, является результатом ошибки или контроля над языком языка CIL.
Ответ 2
Вы находитесь на неизведанной территории здесь, я не знаю ни одного языка .NET, который на самом деле позволяет это. Их синтаксическая проверка отклоняет любой код, который пытается это сделать. Отказано даже добавление двух собственных int. В конечном счете это зависит от дрожания, чтобы сгенерировать для него машинный код. Если вы хотите знать, что происходит, тогда просто экспериментируйте. Обязательно проверьте по крайней мере хладнокровие x86 и x64.
Учитывая семантику iffy и реальную возможность того, что изменение будущего дрожания может нарушить ваши предположения, я настоятельно рекомендую вам также отказаться от этого на своем родном языке. Это просто не очень полезно и простое обходное решение, которое отбрасывает (long), а результат обратно (IntPtr) имеет хорошо определенную семантику. Что само по себе является способом получить предсказуемое поведение в вашем собственном генераторе кода.