Panda Pajama
Panda Pajama

Reputation: 1441

Why can't I compare two IntPtr's with < and >?

I'm currently having a problem with unsafe pointers which appears to be a compiler bug.

Note: the problem is not with the fact that I am using pointers and unsafe code; the code works pretty well. The problem is related to a confirmed compiler bug that refuses to compile legal code under certain circumstances. If you're interested in that problem, visit my other question

Since my problem is with the declaration of pointer variables, I have decided to work around the problem, and use IntPtr instead, and cast to actual pointers when needed.

However, I noticed that I cannot do something like this:

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

The > and < operators don't seem to be defined for IntPtr. Notice that I can indeed compare two actual pointers.

IntPtr has a .ToInt64() method. However, this returns a signed value, which may return incorrect values when comparing with > and < when positive and negative values are involved.

To be honest, I don't really understand what use is there to a .ToInt64() method that returns a signed value, considering that pointer comparisons are performed unsigned, but that's not my question.

One could argue that IntPtrs are opaque handles, and it is therefore meaningless to compare with > and <. However, I would point out that IntPtr has addition and subtraction methods, which mean that there is actually a notion of order for IntPtr, and therefore > and < are indeed meaningful.

I guess I could cast the result of ToInt64() to a ulong and then compare, or cast the IntPtr to a pointer and then do the comparison, but it makes me think why aren't > and < defined for IntPtr.

Why can't I compare two IntPtrs directly?

Upvotes: 10

Views: 3726

Answers (3)

Hans Passant
Hans Passant

Reputation: 941705

Comparing IntPtr is very, very dangerous. The core reason why the C# language disallows this, even though the CLR has no problem with it.

IntPtr is frequently used to store an unmanaged pointer value. Big problem is: pointer values are not signed values. Only an UIntPtr is an appropriate managed type to store them. Big problem with UIntPtr is that it is not a CLS-compliant type. Lots of languages don't support unsigned types. Java, JScript and early versions of VB.NET are examples. All the framework methods therefore use IntPtr.

It is so especially nasty because it often works without a problem. Starting with 32-bit versions of Windows, the upper 2 GB of the address space is reserved to the operating system so all pointer values used in a program are always <= 0x7FFFFFFFF. Works just fine in an IntPtr.

But that is not true in every case. You could be running as a 32-bit app in the wow64 emulator on a 64-bit operating system. That upper 2 GB of address space is no longer needed by the OS so you get a 4 GB address space. Which is very nice, 32-bit code often skirts OOM these days. Pointer values now do get >= 0x80000000. And now the IntPtr comparison fails in completely random cases. A value of, say, 0x80000100 actually is larger than 0x7FFFFE00, but the comparison will say it is smaller. Not good. And it doesn't happen very often, pointer values tend to be similar. And it is quite random, actual pointer values are highly unpredictable.

That is a bug that nobody can diagnose.

Programmers that use C or C++ can easily make this mistake as well, their language doesn't stop them. Microsoft came up with another way to avoid that misery, such a program must be linked with a specific linker option to get more than 2 GB of address space.

Upvotes: 2

FLCL
FLCL

Reputation: 2514

IMHO, IntPtr wasn't developed for such aims like higher/lower comparison. It the structure which stores memory address and may be tested only for equality. You should not consider relative position of anything in memory(which is managed by CLI). It's like comparing which IP in Enternet is higher.

Upvotes: 0

xanatos
xanatos

Reputation: 111870

IntPtr has always been a little neglected. Until .NET 4.0 there weren't even the Add/operator+ and Subtract/operator-.

Now... if you want to compare two pointers, cast them to long if they are an IntPtr or to ulong if they are UIntPtr. Note that on Windows you'll need a UIntPtr only if you are using a 32 bits program with the /3GB option, because otherwise a 32 bits program can only use the lower 2gb of address space, while for 64bit programs, much less than 64 bits of address space is used (48 bits at this time).

Clearly if you are doing kernel programming in .NET this changes :-) (I'm jocking here, I hope :-) )

For the reason of why IntPtr are preferred to UIntPtr: https://msdn.microsoft.com/en-us/library/system.intptr%28v=vs.110%29.aspx

The IntPtr type is CLS-compliant, while the UIntPtr type is not. Only the IntPtr type is used in the common language runtime. The UIntPtr type is provided mostly to maintain architectural symmetry with the IntPtr type.

There are some languages that don't have the distinction between signed and unsigned types. .NET wanted to support them.

Done some tests by using

editbin /LARGEADDRESSAWARE myprogram.exe

(I was even able to crash my graphic adapter :-) )

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));
        }
    }
}

I was able to allocate nearly 4 gb of memory with a 32 bits program (on a 64 bit OS) (so I had negative IntPtr)

And here it is a cast from IntPtr to UIntPtr

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

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

Note that thanks to how sign extension works, you can't simply do (UIntPtr)(ulong)(long)ptr, because at 32 bits it will break.

But note that few programs really support > 2 gb on 32 bits... http://blogs.msdn.com/b/oldnewthing/archive/2004/08/12/213468.aspx

Upvotes: 7

Related Questions