6

It's a simple-looking question:

Given that native-sized integers are the best for arithmetic, why doesn't C# (or any other .NET language) support arithmetic with the native-sized IntPtr and UIntPtr?

Ideally, you'd be able to write code like:

for (IntPtr i = 1; i < arr.Length; i += 2) //arr.Length should also return IntPtr
{
    arr[i - 1] += arr[i]; //something random like this
}

so that it would work on both 32-bit and 64-bit platforms. (Currently, you have to use long.)


Edit:

I'm not using these as pointers (the word "pointer" wasn't even mentioned)! They can be just treated as the C# counterpart of native int in MSIL and of intptr_t in C's stdint.h -- which are integers, not pointers.

user541686
  • 205,094
  • 128
  • 528
  • 886

5 Answers5

6

In .NET 4, arithmetic between a left hand operand of type IntPtr and a right hand operand of integer types (int, long, etc) is supported.

[Edit]: As other people have said, they are designed to represent pointers in native languages (as implied by the name IntPtr). It's fine to claim you're using them as native integers rather than pointers, but you can't overlook that one of the primary reasons the native size of an integer ever matters is for use as a pointer. If you're performing mathematical operations, or other general functions that are independent from the processor and memory architecture that your code is running on, it is arguably more useful and intuitive to use types such as int and long where you know their fixed size and upper and lower bounds in every situation regardless of hardware.

Just as the type IntPtr is designed to represent a native pointer, the arithmetic operations are designed to represent logical mathematical operations that you would perform on a pointer: adding some integer offset to a native pointer to reach a new native pointer (not that adding two IntPtrs is not supported, and nor is using IntPtr as the right hand operand).

jeffora
  • 4,009
  • 2
  • 25
  • 38
  • +1 I didn't know that!! =O But I meant completely using `IntPtr`, not throwing in another fixed-size integer into the mix. – user541686 Apr 08 '11 at 02:56
  • Updated with my thoughts on why using a fixed size integer is appropriate for the situation – jeffora Apr 08 '11 at 03:13
  • @jeffora: So you're basically saying there's no situation in which this would be useful, I understand; right? Though I'd find it hard to swallow, it seems like a very plausible explanation. :) – user541686 Apr 08 '11 at 03:16
  • Not that there's no situation it would be useful, but if you accept the premise that the type is designed to represent a pointer, then there's few situations where it would be logical ;-) – jeffora Apr 08 '11 at 03:19
  • @jeffora: Now I find *that* a little harder to swallow: Is `size_t` supposed to represent a pointer in C? – user541686 Apr 08 '11 at 03:22
  • 1
    Correct me if I'm wrong, but size_t should represent an object's size, and its size is platform dependent. IntPtr is a natively sized integer type, but according to MSDN is "A platform-specific type that is used to represent a pointer or a handle." (http://msdn.microsoft.com/en-us/library/system.intptr.aspx), i.e. pointer or handle, not just any arbitrary platform sized int – jeffora Apr 08 '11 at 03:28
  • 1
    Sure, you can use it as any arbitrary platform sized int, but possibly not expect it to support operations that don't make sense for the specific representations it is designed for. – jeffora Apr 08 '11 at 03:30
  • @jeffora: But then why does `native int` map to `IntPtr` in IL? That's clearly an integer to me, not a pointer... – user541686 Apr 08 '11 at 03:30
  • This is probably getting a bit out of my expertise to theorise on, but I believe there is a lot that can be done in IL that isn't supported in the higher level languages like C#. Perhaps what you need is .NET to provide a core type NativeInt. This could also map to native int in IL, but support more int centric operations. At IL level, it might be the same, but as a C# user it clearly states that IntPtr is for pointers, NativeInt for other situations that native size is required. – jeffora Apr 08 '11 at 03:36
  • @jeffora: Yeah there indeed is a lot that can be done in IL that can't be done in C#... which is why I asked why we can't do this particular thing in C#. :) (Btw, I'm not *insisting* on using `IntPtr`, although now I realize my question made it that way; I meant why can't we have native-sized integers in C#, whatever it might be called.) – user541686 Apr 08 '11 at 03:37
  • I guess the answer would be we can, it just hasn't been implemented yet (as Eric Lippert would say: "because no one ever designed, specified, implemented, tested, documented and shipped that feature" - http://blogs.msdn.com/b/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx) – jeffora Apr 08 '11 at 03:52
  • Nice, I like the Eric Lippert quote. :) – user541686 Apr 08 '11 at 18:48
6

Maybe native-sized integers make for the fastest arithmetic, but they certainly don't make for the most error-free programs.

Personally I hate programming with integer types whose sizes I do not know when I sit down to start typing (I 'm looking at you, C++), and I definitely prefer the peace of mind the CLR types give you over the very doubtful and certainly conditional performance benefit that using CPU instructions tailored to the platform might offer.

Consider also that the JIT compiler can optimize for the architecture the process is running on, in contrast to a "regular" compiler which has to generate machine code without having access to this information. The JIT compiler might therefore generate code just as fast because it knows more.

I imagine I 'm not alone in thinking this, so it might count for a reason.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Now *that's* a plausible explanation (as opposed to the response "pointers are unsafe!" which is completely irrelevant). :) So now my question to you is, how are you supposed to interoperate with a struct that has a `size_t` field in it, that holds, say, an index into some array? (And +1 for the C++ -bash...) – user541686 Apr 08 '11 at 03:05
  • @Mehrdad: First off, I added a third paragraph just now; give it a read as well. – Jon Apr 08 '11 at 03:07
  • I read your third paragraph right now, but it's not *just* an efficiency issue: it's a *correctness* issue for, for example, iterating over arrays that can have arbitrary sizes, in a platform-independent manner. – user541686 Apr 08 '11 at 03:10
  • @Mehrdad: About the `size_t`: I don't know much about interop internals. If you have to map `size_t` to a field of the same size then technically interop in the general case is impossible because the size is unknown; you can only interop against a specific binary wherein the size of `size_t` has been hardcoded by the compiler. If the marshalling is one-way only and the marshaller can store smaller types into larger ones, then you can marshal `size_t` into an `unsigned long` and be OK until we get 128-bit architectures. – Jon Apr 08 '11 at 03:15
  • Yeah exactly -- but then you'd die on 128-bit architectures. Isn't that the exact problem 32-bit code ran into when we migrated to 64-bit? – user541686 Apr 08 '11 at 03:22
1

I can actually think of one reason why an IntPtr (or UIntPtr) would be useful: accessing elements of an array requires native-sized integers. Though native integers are never exposed to the programmer, they are internally used in IL. Something like some_array[index] in C# will actually compile down to some_array[(int)checked((IntPtr)index)] in IL. I noticed this after disassembling my own code with ILSpy. (The index variable is 64-bit in my code.) To verify that the disassembler wasn't making a mistake, Microsoft's own ILDASM tool shows the existence of conv.u and conv.i instructions within my assembly. Those instructions convert integers to the system's native representation. I don't know what the performance implication is having all these conversion instructions in the IL code, but hopefully the JIT is smart enough to optimize the performance penalty away; if not, the next best thing is to allow manipulating native integers without conversions (which, in my opinion, might be the main motivation to use a native type).

Currently, the F# language allows the use of nativeint and and its unsigned counterpart for arithmetic. However, arrays can only be indexed by int in F# which means nativeint is not very useful for the purposes of indexing arrays.

If it really bothers you that much, write your own compiler that lifts restrictions on native integer use, create your own language, write your code in IL, or tweak the IL after compiling. Personally, I think it's a bad idea to squeeze out extra performance or save memory by using native int. If you wanted your code to fit the system like a glove, you'd best be using a lower level language with support for processor intrinsics.

Kevin Li
  • 303
  • 2
  • 12
0

.Net Framework tries not to introduce operations that can't be explained. I.e. there is no DateTime + DateTime because there is no such concept as sum of 2 dates. The same reasoning applies to pointer types - there is no concept of sum of 2 pointers. The fact that IntPtr is stored as platform depenedent int value does not really matter - there are a lot of other types that internally stored as basic values (again DateTime can be represented as long).

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • 1
    Contrary to what you mention, it's actually *not* "stored" as a platform-independent integer (it's stored as a `void*`), but it *is* a platform-independent integer because it's what `native int` maps to from MSIL. I'm not saying anything about the storage mechanism, only the semantics. – user541686 Apr 08 '11 at 04:19
-1

Because that's not a "safe" way of handling memory addressing. Pointer arithmetic can lead to all sorts of bugs and memory addressing problems that C# is designed explicitly to avoid.

Andrew Cooper
  • 32,176
  • 5
  • 81
  • 116
  • It's not the same thing. C is not .NET. .NET is a _managed_ environment. A pointer is not a fixed value in a managed world. You have _references_ that hold values used by the .NET framework to locate objects in memory. Their actual location may be moved at any time by the GC. That's why pointers are unsafe and playing with them is difficult. – Paul Alexander Apr 08 '11 at 02:58
  • @Paul: This has *nothing* to do with pointers! Do you happen to be familiar with MSIL's `native int` data type? It's completely distinct from `void*`... – user541686 Apr 08 '11 at 03:01
  • 1
    Yes, I'm very familiar with the inner workings of the JIT and how it actually generates code. The IntPtr is a _special_ type for holding _pointers_. You're trying to use it for something else. The IntPtr type is designed to provide a safe way for a managed environment to hold a pointer. It is not an arbitrary integer value. – Paul Alexander Apr 08 '11 at 03:05
  • @Paul: When a method returns a `native int` in IL, what's the data type of the return type that you see when you reflect over it? And what does `native int` have to do with pointers? – user541686 Apr 08 '11 at 03:13
  • How is that relevant to your question? Wether you return an int, a ref, an InPtr, or a native int, when it's JITted it's all stored in eax/rax - surprise a native sized register. – Paul Alexander Apr 08 '11 at 03:16
  • @Paul: How is it *not* relevant? A `native int` is a native-sized integer, a pointer is a pointer. You're saying `IntPtr` is not designed for integers, but it it's the precise integer `native int` maps to... – user541686 Apr 08 '11 at 03:24
  • "A native int is a native-sized integer, a pointer is a pointer" that's what I've been saying this whole time. An InpPtr is a _pointer_ not an integer. You aren't supposed to use it as such. And _even if you could_ it wouldn't matter since all the arithmetic operations are still carried out in registers. You will get exactly the same performance using `int` in a 32-bit process or a 64-bit process. The only time it would be slow is if you try and use a `long` in a 32-bit process. Then the JIT has to virtualize the 64-bit operations with multiple 32-bit ops... – Paul Alexander Apr 08 '11 at 03:55
  • ...and as of yet, the JIT compiler does not issue MMX instructions which could potentially speed up 64-bit operations in a 32-bit process. – Paul Alexander Apr 08 '11 at 03:56
  • @Paul: Exactly, I'm referring precisely to the case when you're using `long` in a 32-bit process (or, in the more distant future, using some kind of `long long` in a 64-bit process). And furthermore, I don't understand, why are you saying "*`IntPtr` is a pointer*"? If `native int` maps to `IntPtr`, doesn't that mean it's an integer? And as for MMX, I'm not sure how that's related, but I think that's also something we should be able to do in C# (though that's a different topic). – user541686 Apr 08 '11 at 18:46
  • '`native int` maps to IntPtr'. Pointers are just integers, however they are integers that have been given a semantic meaning. The IntPtr _type_ represents an integer _to be used as a pointer_ not just an arbitrary value. Your use case for using a long in 32-bit just isn't rationale. You couldn't have an array large enough in a 32-bit process to warrant anything more than 32-bit offsets. And if your app is dealing with memory larger than 4GB - it should be running in 64-bit anyway. There's just not rational use case for a native int. – Paul Alexander Apr 08 '11 at 21:05
  • 'And as for MMX, I'm not sure how that's related' - it's related because MMX would allow 32-bit processes to work with up to 256-bit integers using single instructions that are comparable to native int operations on the CPU. It would allow the application to have the same performance using longs as ints. – Paul Alexander Apr 08 '11 at 21:06