【问题标题】:Why can't I compare two IntPtr's with < and >?为什么我不能用 < 和 > 比较两个 IntPtr?
【发布时间】:2015-04-07 06:15:21
【问题描述】:

我目前的 a problem 带有不安全的指针,这似乎是一个编译器错误。

注意:问题不在于我使用了指针和不安全的代码;该代码运行良好。该问题与已确认的编译器错误有关,该错误在某些情况下拒绝编译合法代码。如果您对此问题感兴趣,请访问my other question

由于我的问题在于指针变量的声明,我决定解决这个问题,改用IntPtr,并在需要时转换为实际指针。

但是,我注意到我不能这样做:

IntPtr a = something;
IntPtr b = somethingElse;
if (a > b) // ERROR. Can't do this
{
}

&gt;&lt; 运算符似乎没有为 IntPtr 定义。请注意,我确实可以比较两个实际的指针。

IntPtr 有一个.ToInt64() 方法。但是,这会返回一个 有符号 值,当涉及正值和负值时,与 &gt;&lt; 进行比较时,可能会返回不正确的值。

说实话,考虑到pointer comparisons are performed unsigned,我真的不明白返回有符号值的.ToInt64() 方法有什么用,但这不是我的问题。

有人可能会说IntPtrs 是不透明的句柄,因此与&gt;&lt; 进行比较是没有意义的。不过我要指出的是IntPtr有加减法,也就是说IntPtr其实有一个顺序的概念,所以&gt;&lt;确实有意义。

我想我可以将ToInt64() 的结果转换为ulong 然后进行比较,或者将IntPtr 转换为指针然后进行比较,但这让我想为什么不是&gt;以及为IntPtr 定义的&lt;

为什么我不能直接比较两个IntPtrs?

【问题讨论】:

  • 注意IntPtr是有符号的,所以最后它相当于一个int或一个long。如果您希望它未签名,请使用UIntPtr
  • @xanatos:我不太确定。我认为IntPtr 是一个内部带有void* 的结构。我可以完美地比较void*,所以我认为实际的不安全指针在 CLR 内部是无符号的。不幸的是,大多数与内存相关的函数,例如 Marshal.AllocHGlobal() 返回 IntPtr 而不是 UIntPtr。让我想知道为什么有两种不同的类型,一个指针一个指针一个指针。
  • 在我的回答中添加了另一部分关于签名 IntPtr 的原因
  • @PandaPajama:“我可以完美地比较 void”* - 也许,有点。如果它们指向同一个对象或指向同一个数组中或后一个数组的元素,那么这样的比较就可以了。否则,比较会导致未定义的行为。
  • @EdS:是这样吗? AllocHGlobal 既不返回对象也不返回数组,但我可以在我得到的地址空间内有意义地进行指针比较。

标签: c# pointers


【解决方案1】:

IntPtr一直有点被忽视。在 .NET 4.0 之前,甚至没有 Add/operator+Subtract/operator-

现在...如果你想比较两个指针,如果它们是IntPtr,则将它们转换为long,如果它们是UIntPtr,则将它们转换为ulong。请注意,在 Windows 上,仅当您使用带有 /3GB 选项的 32 位程序时才需要 UIntPtr,因为否则 32 位程序只能使用较低的 2gb 地址空间,而对于 64 位程序,很多使用的地址空间少于 64 位(此时为48 bits)。

很明显,如果您在 .NET 中进行内核编程,这会发生变化 :-)(我在这里开玩笑,我希望 :-))

IntPtr 优于 UIntPtr 的原因:https://msdn.microsoft.com/en-us/library/system.intptr%28v=vs.110%29.aspx

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

有些语言不区分有符号和无符号类型。 .NET 希望支持它们。

使用做一些测试

editbin /LARGEADDRESSAWARE myprogram.exe

(我什至可以让我的图形适配器崩溃 :-))

static void Main(string[] args)
{
    Console.WriteLine("Is 64 bits", Environment.Is64BitProcess);

    const int memory = 128 * 1024;

    var lst = new List<IntPtr>(16384); // More than necessary

    while (true)
    {
        Console.Write("{0} ", lst.Count);

        IntPtr ptr = Marshal.AllocCoTaskMem(memory);
        //IntPtr ptr = Marshal.AllocHGlobal(memory);
        lst.Add(ptr);

        if ((long)ptr < 0)
        {
            Console.WriteLine("\nptr #{0} ({1}, {2}) is < 0", lst.Count, ptr, IntPtrToUintPtr(ptr));
        }
    }
}

我能够使用 32 位程序(在 64 位操作系统上)分配近 4 GB 的内存(所以我有否定的 IntPtr

这里是从IntPtrUIntPtr 的演员表

public static UIntPtr IntPtrToUintPtr(IntPtr ptr)
{
    if (IntPtr.Size == 4)
    {
        return unchecked((UIntPtr)(uint)(int)ptr);
    }

    return unchecked((UIntPtr)(ulong)(long)ptr);
}

请注意,由于符号扩展的工作原理,您不能简单地执行(UIntPtr)(ulong)(long)ptr,因为它会在 32 位时中断。

但请注意,很少有程序真正支持 > 2 GB 的 32 位...http://blogs.msdn.com/b/oldnewthing/archive/2004/08/12/213468.aspx

【讨论】:

  • @PandaPajama 我刚刚检查过,在 Windows 上同时使用 32 位 Marshal.AllocCoTaskMemMarshal.AllocHGlobal,您通常不能拥有 > 2gb 的地址,所以它们都是正数。现在......正如我所说,在 Windows /3gb(或 Linux)上,这种情况发生了变化。在这种情况下,请使用UIntPtr。但请注意,将 IntPtr 转换为 UIntPtr 非常复杂。
  • 为什么不用 .net 进行内核编程? en.wikipedia.org/wiki/Singularity_%28operating_system%29
  • @AK_ 这是我的一个半开玩笑......我知道这在理论上是可能的,但这不是在 SO 上经常被问到的问题
  • 该信息适用于 x86/64,主要是 Windows。我正在开发具有 64 位地址的 Xamarin.iOS
  • 奇点进化了,它真的不再是实验性的了。 “来自可信来源的谣言”说微软在 Azure 内部使用它,它工作得很好,因为你不需要在用户级别和内核级别之间进行隔离(因为你根本无法引用其他空间中的原始内存)——但是意味着你也没有真正的 IntPtr。另外 - 它不是用 C# 编写的,而是用扩展方言编写的。
【解决方案2】:

比较 IntPtr 非常非常危险。 C# 语言不允许这样做的核心原因,即使 CLR 对此没有任何问题。

IntPtr 经常用于存储非托管指针值。大问题是:指针值不是有符号值。只有 UIntPtr 是存储它们的适当托管类型。 UIntPtr 的大问题是它不是符合 CLS 的类型。许多语言不支持无符号类型。 Java、JScript 和早期版本的 VB.NET 就是示例。因此,所有框架方法都使用 IntPtr。

它特别讨厌,因为它通常可以毫无问题地工作。从 32 位版本的 Windows 开始,地址空间的高 2 GB 保留给操作系统,因此程序中使用的所有指针值始终

但并非在所有情况下都是如此。您可以在 64 位操作系统上的 wow64 模拟器中作为 32 位应用程序运行。操作系统不再需要上面 2 GB 的地址空间,因此您获得了 4 GB 的地址空间。这非常好,现在 32 位代码经常绕过 OOM。指针值现在 >= 0x80000000。现在 IntPtr 比较在完全随机的情况下失败。例如,0x80000100 的值实际上大于 0x7FFFFE00,但比较会说它更小。不好。而且它不会经常发生,指针值往往是相似的。而且它是非常随机的,实际的指针值是高度不可预测的。

这是一个没人能诊断出来的错误。

使用 C 或 C++ 的程序员也很容易犯这个错误,他们的语言不会阻止他们。微软想出了另一种方法来避免这种痛苦,这样的程序必须与特定的linker option 链接才能获得超过 2 GB 的地址空间。

【讨论】:

  • Nitpick,在 Java 8 中,您可以通过整数上的整数使用无符号算术来表示无符号值。所以这可能应该是“Java 的早期版本”而不是“Java”。
  • 嗯,Java 8 并没有严重影响 .NET Framework 的设计。
  • 我当然希望它没有:) 我指的是这句话:“很多语言不支持无符号类型。Java、JScript 和早期版本的 VB.NET 就是例子。”
  • 1.不,我不认为我做错了什么;请阅读我的问题。 2.您的回答与我在问题中的表述几乎相同;请阅读我的问题。 3. 根据您回答的内容,我坚信您没有阅读我的问题;请阅读我的问题。顺便说一句,我没有在 Windows 上运行。
  • @Hans:在我的问题中,我声明我知道当符号不正确时签名比较失败;我声明我知道我可以投给ulong。标题说我想知道为什么我不能比较IntPtrs。您似乎错过了这些要点,因为这就是您的答案的全部内容。在您回答后我所做的唯一修改是解决我认为您错过的两点:问题不在于指针本身;我特别想知道为什么我不能比较IntPtrs。
【解决方案3】:

恕我直言,IntPtr 并不是为了更高/更低比较等目标而开发的。它是存储内存地址的结构,只能测试是否相等。您不应该考虑内存中任何东西的相对位置(由 CLI 管理)。就像比较Enternet中哪个IP更高。

【讨论】:

  • 我们使用IntPtr 精确地处理不受 CLI 管理的内存。在 C# 中获取非托管内存的方法有很多,Marshal.AllocHGlobal() 就是其中之一。在大多数情况下,指针和不安全代码不是必需的,但在某些情况下它们是必需的。顺便说一下,链路层中不存在IP地址;也许你的意思是 MAC 地址?
  • @PandaPajama 好吧,我只在 PInvoke 调用中使用 IntPtr,在你的情况下最好使用 msdn.microsoft.com/en-us/library/deh4fbw8.aspx 之类的东西。
  • 我想向您指出我的问题的前两段,我在其中解释了为什么我不能按照您的提议进行
猜你喜欢
  • 2012-06-10
  • 1970-01-01
  • 2021-04-18
  • 2019-10-24
  • 1970-01-01
  • 2013-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多