9

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?

Community
  • 1
  • 1
Panda Pajama
  • 1,283
  • 2
  • 13
  • 31
  • Note that `IntPtr` is signed, so in the end it is equivalent to a `int` or a `long`. If you want it to be unsigned, use `UIntPtr` – xanatos Apr 07 '15 at 06:24
  • @xanatos: I'm not so sure about that. I think `IntPtr` is a struct with a `void*` inside of it. I can compare `void*` flawlessly, so I think that actual unsafe pointers are unsigned inside the CLR. Unfortunately most memory related functions, like `Marshal.AllocHGlobal()` return `IntPtr` and not `UIntPtr`. Makes me wonder why there are two different types, a pointer is a pointer is a pointer. – Panda Pajama Apr 07 '15 at 06:32
  • Added another part to my answer about the reason of the signed IntPtr – xanatos Apr 07 '15 at 06:38
  • @PandaPajama: *"I can compare void* flawlessly"* - Maybe, kinda. If they point to the same object or point to elements within or one past the end of the same array, such a comparison is fine. Otherwise, the comparison results in undefined behavior. – Ed S. Apr 07 '15 at 06:40
  • @EdS: Is that so? `AllocHGlobal` returns neither an object nor an array, yet I can meaningfully do pointer comparisons inside the address space I got. – Panda Pajama Apr 07 '15 at 06:47
  • @PandaPajama: When you said "compare void", I thought you were referring to C or C++. What `AllocHGlobal` is doing under the covers I don't know. – Ed S. Apr 07 '15 at 17:36
  • @Ed: from a purely theoretical point of view, one could say that the result is platform defined. From a more practical perspective, your IntPtrs are most likely just pointers in your virtual address space, and therefore you can actually perform arithmetic on them. However, since most memory allocators don't make guarantees as of the locations you will get, performing arithmetic between pointers of different objects is unlikely to be meaningful – Panda Pajama Apr 07 '15 at 18:45
  • @PandaPajama: Again, as far as C and C++ *the languages* are concerned, it is UB. – Ed S. Apr 07 '15 at 20:48
  • @Ed so that means that every time I've compared pointers inside the address space provided by API functions such as malloc, I've been relying on undefined behavior? – Panda Pajama Apr 08 '15 at 01:55
  • @PandaPajama: If you compared to pointers which refer to addresses within the same block allocated by one call to `malloc`, then no. If you compare two pointers which point to different objects or blocks, then yes. – Ed S. Apr 08 '15 at 03:58
  • @Ed: That's precisely what I'm talking about. However, from an academic point of view, the language doesn't know what `malloc` is returning, therefore as far as the language is concerned, the pointers returned are not either the same object nor a (language) array. This is just a super pedantic conversation; we both know what we're talking about and we're stating exactly the same thing. – Panda Pajama Apr 08 '15 at 05:38
  • @PandaPajama: The behavior of `malloc` is defined by the standard, so it really does. Language implementors have to implement the standard. I think you're focusing on implementation specific behavior, but there is a difference between implementation defined and undefined behavior. I don't think it is pedantic at all; these differences matter. – Ed S. Apr 08 '15 at 05:50
  • @Ed: Not really. Comparing pointers across objects is unlikely to have any meaning. Discussing the semantics of a meaningless action based on the letter of the standard amply falls within my definition of pedantic. I've never done it, and is certainly not what I'm doing. That doesn't mean that `IntPtr`s should not be order comparable. A perfect C# `IntPtr` with notions of order and sequence would allow me to compare with `<` and `>`, and throw an exception when comparing across objects. – Panda Pajama Apr 08 '15 at 07:20

3 Answers3

7

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

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • @PandaPajama I've just checked, using both `Marshal.AllocCoTaskMem` and `Marshal.AllocHGlobal` at 32 bits on Windows you normally can't have addresses > 2gb, so they are all positive. Now... as I've said, on Windows /3gb (or on Linux) this changes. In this case, use `UIntPtr`. But note that casting `IntPtr` to `UIntPtr` is quite complex. – xanatos Apr 07 '15 at 06:56
  • 1
    why not do kernel programming with .net? http://en.wikipedia.org/wiki/Singularity_%28operating_system%29 – AK_ Apr 07 '15 at 08:11
  • @AK_ It was a semi-joke of mine... I do know it is theorically possible, but it isn't something that is asked often on SO – xanatos Apr 07 '15 at 08:14
  • That information is valid for x86/64, and mostly Windows. I am working on Xamarin.iOS, which has 64 bit addresses – Panda Pajama Apr 07 '15 at 09:28
  • Singularity evolved and it's really not experimental any more. "Rumors from trusted sources" say Microsoft are using it internally, in Azure, and it works great since you don't need isolation between user level and kernel level (since you simply can't reference raw memory in other space) - but that means you also don't really have IntPtr. Also - it's not written in C#, it's written in an extended dialect. – Benjamin Gruenbaum Apr 07 '15 at 12:11
2

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Nitpick, in Java 8 you can use unsigned arithmetic through integer on ints to represent unsigned values. So that should probably be "earlier versions of Java" and not "Java". – Benjamin Gruenbaum Apr 07 '15 at 12:13
  • Hmm, Java 8 did not heavily affect the .NET Framework design. – Hans Passant Apr 07 '15 at 12:40
  • I sure hope it didn't :) I'm referring to this sentence: " Lots of languages don't support unsigned types. Java, JScript and early versions of VB.NET are examples. " – Benjamin Gruenbaum Apr 07 '15 at 12:41
  • 1. No, I don't think I'm doing anything wrong; please read my question. 2. Your answer is stating pretty much the same I'm stating in my question; please read my question. 3. Based on the content of your answer, I strongly believe that you did not read my question; please read my question. And by the way, I'm not running on Windows. – Panda Pajama Apr 07 '15 at 12:52
  • @Hans: In my question I state that I am aware that signed comparisons fail when the signs are incorrect; I state that I am aware that I could cast to `ulong`. The very title states that I want to know why I can't compare `IntPtr`s. You seemed to miss those points, because that's what your answer is all about. The only edit I made after your answer was to address the two points I think you missed: the problem is not with pointers themselves; and I am specifically interested in knowing why I can't compare `IntPtr`s. – Panda Pajama Apr 07 '15 at 13:39
  • @Hans: The entire question explains that this is not a problem I am facing, just a request for a clarification. But even after asking you to read my question, you keep on insisting that I have some kind of problem that needs to be solved. I don't. I just want to know why something is implemented in a particular manner, that's why my question is not "How do I", but "why". I very much welcome your input, but I would appreciate it if you were not condescending with it. – Panda Pajama Apr 07 '15 at 13:44
  • I saw yor edit, and I understand your point. However, comparisons are not allowed on UIntPtr either. As I understand the concept behind IntPtr, it is not supposed to either have a sign or not. It should jyst be an opaque container with notions of order and sequence, but not tied to a particular representation. Clearly that was not the actual result. – Panda Pajama Apr 07 '15 at 18:49
0

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.

FLCL
  • 2,445
  • 2
  • 24
  • 45
  • We use `IntPtr` precisely to handle memory that is -not- managed by CLI. There are many ways to get non-managed memory in C#, one of which is `Marshal.AllocHGlobal()`. In most scenarios pointers and unsafe code are not necessary, but there are some cases in which they are. By the way, IP addresses do not exist in the link layer; maybe you meant MAC address? – Panda Pajama Apr 07 '15 at 09:18
  • @PandaPajama Well, am using IntPtr only for PInvoke calls, think better to use something like https://msdn.microsoft.com/en-us/library/deh4fbw8.aspx in your case. – FLCL Apr 07 '15 at 09:35
  • I would like to point you to the first two paragraphs of my question, where I explain why I cannot do what you are proposing – Panda Pajama Apr 07 '15 at 09:58