c# - C# 中的 64 位指针算法,检查算术溢出更改行为

标签 c# math 64-bit

我有一些不安全的 C# 代码,这些代码在 64 位机器上运行的 byte* 类型的大内存块上进行指针运算。它在大多数情况下都可以正常工作,但是当事情变大时,我经常会遇到指针不正确的某种损坏。

奇怪的是,如果我打开“检查算术溢出/下溢”,一切正常。我没有得到任何溢出异常。但由于性能影响很大,我需要在没有此选项的情况下运行代码。

是什么导致了这种行为差异?

最佳答案

checked 和 unchecked 之间的区别实际上是 IL 中的一个错误,或者只是一些糟糕的源代码(我不是语言专家所以我不会评论 C# 编译器是否生成了正确的 IL对于模棱两可的源代码)。我使用 4.0.30319.1 版本的 C# 编译器编译了这个测试代码(尽管 2.0 版本似乎做同样的事情)。我使用的命令行选项是:/o+/unsafe/debug:pdbonly。

对于未检查的 block ,我们有以下 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 处,add 获得 2 个操作数,一个是 byte* 类型,另一个是 uint32 类型。根据 CLI 规范,它们实际上分别标准化为 native int 和 int32。根据 CLI 规范(准确地说是分区 III),结果将是 native int。因此第二个操作数必须提升为 native int 类型。根据规范,这是通过符号扩展来实现的。因此 uint.MaxValue(在有符号表示法中为 0xFFFFFFFF 或 -1)被符号扩展为 0xFFFFFFFFFFFFFFFF。然后添加 2 个操作数 (0x0000000008000000L + (-1L) = 0x0000000007FFFFFFL)。 conv 操作码仅用于验证目的,将 native int 转换为 int64,在生成的代码中为 nop。

现在对于检查 block ,我们有这个 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)

除了 add 和 conv 操作码外,它几乎完全相同。对于添加操作码,我们添加了 2 个“后缀”。第一个是“.ovf”后缀,其含义很明显:检查溢出,但也需要'启用第二个后缀:“.un”。 (即没有“add.un”,只有“add.ovf.un”)。 “.un”有两个作用。最明显的一个是加法和溢出检查是在操作数是无符号整数的情况下完成的。从我们的 CS 类(class)开始,希望我们都记得,由于二进制补码编码,有符号加法和无符号加法是相同的,所以“.un”真的只影响溢出检查,对吧?

错了。

请记住,在 IL 堆栈上我们没有 2 个 64 位数字,我们有一个 int32 和一个 native int(规范化后)。那么“.un”意味着从 int32 到 native 的转换被视为“conv.u”而不是上面默认的“conv.i”。因此 uint.MaxValue 从零扩展为 0x00000000FFFFFFFFFL。然后添加正确地产生 0x0000000107FFFFFFL。 conv 操作码确保无符号操作数可以表示为带符号的 int64(它可以)。

您的修复仅适用于 64 位。在 IL 级别,更正确的修复方法是将 uint32 操作数显式转换为 native int 或无符号 native int,然后 check 和 unchecked 对 32 位和 64 位的行为相同。

关于c# - C# 中的 64 位指针算法,检查算术溢出更改行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6321650/

相关文章:

c# - 将文件预处理代码从 C 移植到 C#

c# - 全文搜索和 Entity Framework v1 : is it possible?

java - 查找阶乘中的尾随零

windows - 有什么工具可以将 32 位/64 位可执行文件打包在一起吗?

c# - 如何从 x64 构建应用程序?

c# - StackExchange.Redis 与 Azure Redis 速度慢得无法使用或引发超时错误

java - 如何从距离和角度获取随机点?

c++ - 使用 SVD 计算最佳拟合平面方程时出错

c# - 上下文菜单 Shell 扩展在 Windows 7 64 位下不起作用

c# - 为什么 Type 类不在 System.Reflection 命名空间中?