【问题标题】:Difference between IntPtr and UIntPtrIntPtr 和 UIntPtr 的区别
【发布时间】:2012-10-21 16:04:53
【问题描述】:

当我注意到页面上的这条评论时,我正在查看RegOpenKeyEx 的 P/Invoke 声明:

IntPtr 更改为UIntPtr:使用IntPtr 调用句柄时,您将遇到溢出。如果您希望它在 32 位和 64 位平台上正常工作,UIntPtr 是正确的选择。

这对我来说没有多大意义:IntPtrUIntPtr 都应该表示指针,因此它们的大小应该与操作系统的位数相匹配 - 32 位或 64 位。由于这些不是数字而是指针,因此它们的带符号数值无关紧要,只有表示它们指向的地址的位。我想不出这两者之间会有什么不同的任何原因,但这个评论让我不确定。

使用UIntPtr 代替IntPtr 是否有特定原因?根据documentation

IntPtr 类型符合 CLS,而 UIntPtr 类型不符合。在公共语言运行库中仅使用 IntPtr 类型。提供UIntPtr 类型主要是为了与IntPtr 类型保持架构对称。

当然,这意味着没有区别(只要有人不尝试将值转换为整数)。那么上面来自pinvoke.net的评论不正确吗?

编辑

阅读MarkH's answer后,我做了一些检查,发现.NET应用程序不能识别大地址,在32位模式下编译时只能处理2GB的虚拟地址空间。 (可以使用hack 来打开大地址感知标志,但 MarkH 的回答表明 .NET Framework 内部的检查会破坏事情,因为假定地址空间只有 2GB,而不是 3GB。)

这意味着指针可以拥有的所有正确的虚拟内存地址(就 .NET Framework 而言)将介于 0x00000000 和 0x7FFFFFFF 之间。当此范围转换为带符号的int 时,没有值会是负数,因为最高位未设置。这强化了我的信念,即使用 IntPtr 和 UIntPtr 没有区别。我的推理正确吗?

Fermat2357指出上面的编辑是错误的。

【问题讨论】:

  • @Fermat2357 是的,正是这个问题促使我输入了一个新问题。
  • 我认为你是对的。完全没有区别。 HANDLE 只是资源的唯一编号(当然在 Win32 中它通常实现为指向内存区域的 32 位指针)。但是,IntPtr 能够嵌入任何类型的 32 位数字。与 UIntPtr 的区别只是对这个数字的解释而已。

标签: c# .net pointers intptr


【解决方案1】:

UIntPtrIntPtr 内部实现为

private unsafe void* m_value;

您都是对的,只是只管理代表地址的位。

我唯一能想到的溢出问题是如果您尝试执行指针算术。这两个类都支持添加和减去偏移量。但同样在这种情况下,经过这样的操作,二进制表示应该是可以的。

根据我的经验,我也更喜欢UIntPtr,因为我认为指针是无符号对象。但这无关紧要,只是我的意见。

在您的情况下使用IntPtrUIntPtr 似乎没有任何区别。

编辑:

IntPtr 符合 CLS,因为 CLR 之上的某些语言不支持无符号。

【讨论】:

  • 我发现微软所做的这种框架设计非常令人不安——为了对称而使用重复的无意义类型来包装指针。他们应该只创建一个包装器并将其命名为 SafePtr,而不是进入完全没有意义的已签名/未签名区域。
【解决方案2】:

当然,这意味着没有区别(只要有人不尝试将值转换为整数)。

不幸的是,框架试图做到这一点(当专门为 x86 编译时)。 IntPtr(long) 构造函数和ToInt32() 方法都尝试将值转换为checked 表达式中的int。这是使用框架调试符号时看到的实现。

    public unsafe IntPtr(long value)
    { 
        #if WIN32
            m_value = (void *)checked((int)value);
        #else
            m_value = (void *)value; 
        #endif
    } 

当然,如果值超出范围,检查表达式会抛出异常。 UIntPtr 不会溢出相同的值,因为它会尝试转换为 uint

【讨论】:

  • 我想知道该检查是否到位,因为用户虚拟地址空间从 0x00000000 变为 0x7FFFFFFF。由于未在有效的虚拟内存地址中设置第 31 位,checked 转换在 32 位操作系统上不应失败。我不确定 .NET 是否支持 /3GB,因为它扩展了虚拟地址空间。
  • 有两个演员。一个public IntPtr(long value) 和第二个public IntPtr(int value)。第一个 ctor 只接受 64 位值是完全正常的(对于 32 位窗口),这些值可以转换为 32 位而不会丢失精度。
  • @Fermat2357 是的,没错,但checked 转换不是关于精度损失,而是关于验证地址是否在有效地址范围内。理论上,在 32 位系统上检查不应该失败,因为不应该在用户空间中设置最高位。
  • 我刚刚检查过,.NET 应用程序不能识别大地址(只能使用 editbin 破解标志,所以我认为上述 checked 转换在 32 位系统上永远不会失败,即使操作系统使用/3GB 运行 - 由于应用程序不知道大地址,它会获得常规的 2GB 虚拟地址空间。
  • UIntPtr 对于相同的值不会溢出,因为它会尝试转换为 uint 这不是问题,除非你使用带有longctor 不适合int。顺便说一下UIntPtr也可以溢出。它的实现方式与签名案例中的checked 语句相同。
【解决方案3】:

IntPtr\UIntPtr 之间的区别与 Int32\UInt32 之间的区别相同(即,这完全取决于数字的解释方式。)

通常,您选择哪个并不重要,但如前所述,在某些情况下,它可能会反过来咬您。

(我不确定为什么 MS 选择 IntPtr 开头(CLS 合规性等),内存被处理为 DWORD(u32),这意味着它是无符号的,因此,首选方法应该是 UIntPtr,而不是 IntPtr对吧?)

偶数:UIntPtr.Add()

对我来说似乎是错误的,它需要一个 UIntPtr 作为指针,以及一个“int”作为偏移量。 (什么时候,对我来说,'uint' 会更有意义。为什么要将有符号值提供给无符号方法,当代码很可能将其转换为引擎盖下的 'uint' 时。/facepalm)

我个人更喜欢 UIntPtr 而不是 IntPtr,因为无符号值与我正在使用的底层内存的值相匹配。 :)


顺便说一句,我最终可能会创建自己的指针类型(使用 UInt32),专门为直接使用内存而构建。 (我猜 UIntPtr 不会捕获所有可能的坏内存问题,即 0xBADF00D 等,等等。这是一个等待发生的 CTD ......我将不得不看看内置类型首先处理事情,希望空\零检查正确过滤掉这样的东西。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 2021-11-09
    • 1970-01-01
    相关资源
    最近更新 更多