13

int32向64 位添加 时native int,CLR 是对 32 位整数进行符号扩展还是零扩展?最重要的是:根据什么信息做出这个选择?


我正在编写一个 .NET 编译器并已通读 ECMA 规范,但找不到答案。

CLI 在其对存储在其评估堆栈上的值的操作中仅支持这些类型的子集:int32int64native int
-- ECMA 335,第 I 节 12.1:支持的数据类型

由于评估堆栈上的值没有关于其符号性的信息,因此操作数的符号性重要的指令有两种变体:一种用于有符号整数,另一种用于无符号整数。add,sub和指令(那些不检查溢出的mul指令)不需要关心操作数的符号性,只要操作数大小相同,因此只有一个变体。但是,操作数并不总是相同的大小......

ECMA 335,第 III 节 1.5:操作数类型表规定 anint32和 anative int可以相加、相减、相乘和相除。结果又是一个native int。在 64 位系统上,anative 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,int64native int(以及其他一些与此无关的内容)。


我想算术IntPtrUIntPtr因为它在内部表示为本地整数,也会使用这种加法。但是,ILSpy 表明在 C# 中添加 anIntPtr和 anInt32会调用类上的重载 + 运算符,该运算符IntPtr仅接受带符号的Int32参数。

直接在 CIL 中执行(使用add指令)也表明整数被解释为有符号。它也应该在 Mono 中实现,但我找不到任何参考资料来支持我的发现。

4

2 回答 2

5

添加相同位大小的两个值时,符号性无关紧要。例如,将 32 位 -10 ( 0xfffffff6) 添加到 32 位 10 ( 0x0000000a) 将正确产生 0。因此,CIL(通用指令语言)中只有一条add指令。

但是,当添加两个不同位大小的值时,符号性确实很重要。例如,将 32 位 -10 加到 64 位 10 可以在0x100000000完成无符号时产生 4294967296 ( ),而在有符号时产生 0。

CILadd指令允许将本机整数32 位整数相加。本机整数可能是 64 位(在 64 位系统上)。测试表明add将 32 位整数视为有符号整数,并对其进行符号扩展。 这并不总是正确的,可能被认为是一个错误。微软目前不会修复它。

因为溢出检查取决于操作数被视为无符号还是有符号,所以有两种变体add.ovf:(有add.ovf符号)和add.ovf.un(无符号)。但是,当将 32 位整数添加到本机整数时,这些变体也正确地对较小的操作数进行零扩展的符号扩展。

因此,根据 C# 的溢出检查设置,添加本机整数和无符号 32 位整数可能会产生不同的结果。显然,我无法弄清楚这一点是 CIL 语言设计中的错误或疏忽的结果。

于 2013-03-04T14:07:40.303 回答
2

您在这里处于未知领域,我不知道任何实际允许这样做的 .NET 语言。他们的语法检查器拒绝任何试图这样做的代码。即使添加两个本机整数也会被拒绝。最终由抖动来为其生成机器代码。如果您想知道会发生什么,那么只需进行实验。请务必至少测试 x86 和 x64 抖动。

鉴于不确定的语义以及未来抖动变化可能会打破您的假设的非常现实的可能性,我强烈建议您也用自己的语言拒绝这一点。它只是不是很有用,并且转换为 (long) 并且返回到 (IntPtr) 的结果具有明确定义的语义的简单解决方法。这本身就是一种在您自己的代码生成器中获得可预测行为的方法。

于 2013-01-07T08:28:58.083 回答