7

Preface

So after a long time of C only work I came back to Delphi and found out that there are some new things in Delphi. One being NativeInt.

To my surprise I discovered that Delphi and C handle their "native integer"1 types different for x86-64. Delphi NativeInt seems to behave like C void * and Delphi Pointer which is contrary to what I would expect from the names.

In Delphi NativeInt is 64 bit in size. Expressed in Code:

SizeOf(NativeInt) = SizeOf(Pointer) = SizeOf(Int64) = 8

C has only 64 bit pointers. int remains 32 bit. Expressed in Code2:

sizeof(int) == 4 != sizeof(void *) == 8

Even the Free Pascal Compiler3 agrees on the size of NativeInt.

Question

Why was 64 bit chosen for Delphi NativeInt and 32 bits for C int?

Of course both are valid according to the language documentation/specification. However, "the language allows for it" is not really a helpful answer.

I guess it has to do with speed of execution as this is the main selling point of C today. Wikipedia and other sources all say that x86-64 do have 64 bit operand registers. However, they also state that the default operand size is 32 bit. So maybe operations on 64 bit operands are slower compared to 32 bit operands? Or maybe the 64 bit registers can do 2 32 bit operations at the same time? Is that a reason?

Is there maybe another reason the creators of the compilers did choose these sizes?

Footnotes

  1. I am comparing Delphi NativeInt to C int because the name/specificaion suggests that they have similar purpose. I know there is also Delphi Integer which behaves like C int on x68 and x86-64 in Delphi.
  2. sizeof() returns the size as multiple of char in C. However, char is 1 byte on x86-64.
  3. It does so in Delphi mode and default mode for NativeInt. The other integer types in default mode are a whole other can of worms.
J...
  • 30,968
  • 6
  • 66
  • 143
RotatingPieces
  • 413
  • 2
  • 8
  • 3
    `NativeInt` is perhaps more like `intptr_t` from `` standard header (C99 standard), and `intptr_t` might be bigger than `int` (but it is the same size than `void*`) – Basile Starynkevitch Sep 12 '13 at 18:04
  • 3
    SizeOf(Char) = 2 since D2009. NativeInt and NativeUInt are buggy until D2009. – LU RD Sep 12 '13 at 18:13
  • 3
    ... this is the reason why I prefer the `PtrInt PtrUInt` syntax of FPC. This is the right way in FPC: FPC compiled for Win64 years before Delphi! "NativeInt" is for compatibility with Delphi, only. What does "native" mean anyway? http://blog.synopse.info/post/2010/08/10/Writing-Delphi-code-for-64-bits-compiler – Arnaud Bouchez Sep 12 '13 at 18:41
  • 1
    @LURD No, what RP means is that `sizeof(char)` is, by definition, equal to 1 on C. Even if `char` is 16 bits wide then `sizeof(char)` is 1. In other words, `sizeof()` measures size in units of `char`. – David Heffernan Sep 12 '13 at 18:42
  • 1
    @ArnaudBouchez We all know that Emba have a very strange idea of what native is?! Apparently FMX is native!! – David Heffernan Sep 12 '13 at 18:42
  • @DavidHeffernan, I was not sure if RP was talking about C or Delphi, but SizeOf() gives the result in bytes in Delphi. – LU RD Sep 12 '13 at 18:45
  • @LURD He wrote "sizeof() returns the size as multiple of char in C". That's as clear as can be. – David Heffernan Sep 12 '13 at 18:47
  • @Arnaud Bouchez **What does "native" mean anyway?** Agreed. See my commets below. – RotatingPieces Sep 12 '13 at 18:48
  • @LU RD **I was not sure if RP was talking about C or Delphi** I explicitly said that I mean the C sizeof. – RotatingPieces Sep 12 '13 at 18:48
  • Delphi XE2 has a UIntPtr type (which maps to NativeUInt). – Johan Sep 12 '13 at 19:47
  • 2
    @RotatingPieces, `native` means in line with the bit size of the processor. i.e. 32bit on a 32bit processor and 64bit on x86-64. On a 128 bit processor it would be twice as big still. – Johan Sep 12 '13 at 19:49
  • @LURD: `NativeInt` and `NativeUInt` still had some bugs in D2009. D2010 is when they finally stabilized. – Remy Lebeau Sep 13 '13 at 00:52
  • @RemyLebeau, ok. I was actually quoting you, [`Delphi 7: SizeOf(NativeInt) = 8`](http://embarcadero.newsgroups.archived.at/public.delphi.rtl/201104/1104134907.html). :-) – LU RD Sep 13 '13 at 05:16
  • @LURD: I never said that. `NativeInt` did not exist in D7, and if it did then `SizeOf(NativeInt)` would have been 4 since D7 was 32bit only. – Remy Lebeau Sep 13 '13 at 06:39
  • @RemyLebeau, did you follow the link I provided? – LU RD Sep 13 '13 at 06:57
  • @Johan: I doubt there ever will be a X32 port, till then, we won't know (x32 is a 32-bits pointer model in 64-bits mode, iow a 64-bits CPU running in 64-bit mode). The pointer size definition IMHO is cleaner, and doesn't provoke a discussion on computer architectures. – Marco van de Voort Sep 13 '13 at 11:20
  • I agreed with Johan. C doesn't enforce that `int` must be the native size since in 64-bit systems it's typically still 32 bits, and on 8-bit microcontrollers it's 16 bits – phuclv May 27 '14 at 06:00

2 Answers2

10

NativeInt is simply an integer that is the same size as a pointer. Hence the fact that it changes size on different platforms. The documentation says exactly that:

The size of NativeInt is equivalent to the size of the pointer on the current platform.

The main use for NativeInt is to store things like operating system handles that behind the scenes are actually memory addresses. You are not expected to use it to perform arithmetic, store array lengths etc. If you attempt to do that then you make it much more difficult to share code between 32 and 64 bit versions of your program.

You can think of Delphi NativeInt as being directly equivalent to the .net type IntPtr. In C and C++ the OS handle types would commonly be declared as void* which is a pointer type rather than an integer type. However, you would perfectly well use a type like intptr_t if you so wished.

You use the term "native integer" to describe NativeInt, but in spite of the name it's very important to realise that NativeInt is not the native integer type of the language. That would be Integer. The native in NativeInt refers to the underlying hardware platform rather than the language.

The Delphi type Integer, the language native integer, matches up with the C type int, the corresponding language native type. And on Windows these types are 32 bits wide for both 32 and 64 bit systems.

When the Windows designers started working on 64 bit Windows, they had a keen memory of what had happened when int changed from 16 to 32 bits in the transition from 16 bit to 32 bit systems. That was no fun at all, although it was clearly the right decision. This time round, from 32 to 64, there was no compelling reason to make int a 64 bit type. Had the Windows designers done so, it would have made porting much harder work. And so they chose to leave int as a 32 bit type.

In terms of performance, the AMD64 architecture was designed to operate efficiently on 32 bit types. Since a 32 bit integer is half the size of a 64 bit integer, then memory usage is reduced by making int only 32 bits on a 64 bit system. And this will have a performance benefit.

A couple of comments:

  • You state that "C has only 64 bit pointers". That is not so. A 32 bit C compiler will generally use a flat 32 bit memory model with 32 bit pointers.
  • You also say, "in Delphi NativeInt is 64 bit in size". Again that is not so. It is either 32 or 64 bits wide depending on the target.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • **You state that "C has only 64 bit pointers". That is not so.** I should have worded it in a different way. I meant to say: In C void * is 64 bit and int remains 32 bit. – RotatingPieces Sep 12 '13 at 18:31
  • **NativeInt is simply an integer that is the same size as a pointer.** That makes NativeInt a unintuitive name for that type then. However, it is probably the most reasonable awnser. – RotatingPieces Sep 12 '13 at 18:35
  • 1
    You need to be careful there. For C on Windows, `int` is always 32 bit. That's the platform standard. However, it would be possible, albeit useless, to write a standards compliant C compiler for Windows for which `int` was other than 32 bits. If I recall correctly, `int` has to be at least 16 bits wide, but that's the only restriction placed by the standard, beyond relative orderings like `sizeof(int) >= sizeof(short)` and so on. – David Heffernan Sep 12 '13 at 18:36
  • 3
    @RotatingPieces It all comes down to what you mean by native. In the Delphi designers minds that word is meant to imply "hardware native". But your inference was "language native" which is also perfectly plausible to my mind. Anyway, there it is. That's what we've got. – David Heffernan Sep 12 '13 at 18:37
  • **Did you mean to say more than that?** Not really. In already tried to keep the scope of my question narrow because I wanted to know a reason and not a discussion on all integer types. **I wrote the "Did you mean to say more than that?" in response ...** Yeah, turns out I can't hit return in these edit boxes to insert newlines. – RotatingPieces Sep 12 '13 at 18:39
  • 1
    I wrote the "Did you mean to say more than that?" in response to the half comment that appeared when you accidentally pressed enter. You've now edited it. Hence my comment has disappeared. – David Heffernan Sep 12 '13 at 18:40
  • **@RotatingPieces It all comes down to what you mean by native. In the Delphi designers minds that word is meant to imply "hardware native".** I don't mint the "Native". Its just that "NativeInt" doesn't contain any hint of a relation to pointers at all. Something like "PointerSizedInt" would have made more sense for me. – RotatingPieces Sep 12 '13 at 18:42
  • 2
    Agreed. I'd have called it `IntPtr` myself. – David Heffernan Sep 12 '13 at 18:43
  • **You also say, "in Delphi NativeInt is 64 bit in size"** I tried to make it clear in question text and header that I talk about x86-64. I wanted to keep the topic narrow and not to encourage someone to quote rarely used types from stdint.h or tell tales of what his fpc does on some rare processor. – RotatingPieces Sep 12 '13 at 18:56
  • 2
    It's impossible to discuss any of this without considering the other platforms. Otherwise how could you explain the existence of both `NativeInt` and `Int64`? I'm sure that you already know most of what I said, and quite a lot more beside. But the answer is not just for you, and not even mostly for you. Answers here will endure and when somebody much less experienced than you wonders what `NativeInt` is, they might land here. All you needed was the documentation link. But that doesn't make for a very helpful answer to less experienced programmers. You see what I mean? – David Heffernan Sep 12 '13 at 19:10
  • Afaik in C, int is defined as <=long and not fixed 32-bit. Some 64-bit architectures may have 64-bit int. This is called the ILP model, but these are considered "exotic". – Marco van de Voort Sep 13 '13 at 02:18
  • @Marco I don't think I said that C int is 32 bits. In fact I said otherwise many times. But on Windows 32 and 64 bit, C int is 32 bit. On other platforms, as I have said, it has different sizes. – David Heffernan Sep 13 '13 at 06:16
0

Note that NativeInt is not meant to interact with a pointer!

The issue is that nativeInt is signed.
Normally this is not what you want, because the pointer points the the beginning of the datablock. Negative offsets will net you an access violation here.
If you have a pointer pointing to the middle (because you're doing indexing or something like that) then negative offsets apply and NativeInt aka IntPtr comes into view.

For standard pointers (pointing to the start): use UIntPtr, because it will not breakdown when the offset becomes bigger than 2^31/2^63.
(Likely on a 32bit platform, not so much on 64bit)

For this reason there is a UIntPtr, which maps exactly to the C equivalent.
The UIntPtr is a NativeUint.

Use cases
Which of the types you choose to use depends on the use case.

A: I want the fastest integer -> Choose Int32 aka integer;
B1: I want to have an integer to do pointer arithmetic -> Choose UIntPtr aka NativeUInt*.
B2: I do indexing with my pointer -> Choose IntPtr aka NativeInt.
C: I want a big integer, but don't want the big slowdown that Int64 gives me on X86 -> choose NativeInt. D: I want a bigint: choose Int64. (but know that it will be slowish on X86).

*) if you want to make it clear to the reader of your code that you're messing with pointers you need to name it UIntPtr obviously.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • 1
    If you want to do pointer arithmetic use a pointer. If you want to store an opaque pointer in an integral type, NativeInt is fine. Also, for offsets you absolutely want a signed value. – David Heffernan Sep 12 '13 at 20:51
  • 2
    **The issue is that nativeInt is signed (and negative offsets to a pointer do not make sense).** Negative values don't make sense in theory. However, in praxis that gets really messy really fast and you wish they were. I often see it in C where the type size_t is unsigned. When using something like rev() or pointer arithmetic your get a ssize_t which signed. And then you have compare a ssize_t and a size_t to check if you did access to much/little memory and the compiler starts complaining about comparing types with and without signedness. – RotatingPieces Sep 12 '13 at 20:56
  • Denying the existence of negative numbers is masochistic. What's the difference between 1 and 2? Try answering without negative numbers. Why do we habitually use signed integers? Why did Java omit signed values altogether? Answers on a postcard.... :-) – David Heffernan Sep 12 '13 at 21:13
  • There is `NativeUInt` which is an unsigned, pointer-sized type. If you need to store a pointer in an integer type, IMO this is the correct one to use - not `NativeInt`. @DavidHeffernan, while in general I agree and prefer signed types as more 'natural', unsigned types have their place, rare though it is, especially for addresses. @RotatingPieces, negative offsets to a pointer *do* make sense. Try navigating an indexed, self-referential binary file or data structure. – David Sep 13 '13 at 00:13
  • @DavidM It wasn't RP that said negative offsets don't makes sense. RP's comment was disputing that. The bold was a quote. As for your comment to me, if you need to store a pointer, use a pointer. If you are putting a pointer in an integer it is usually because somebody else decided the type. For example lpData param in windows, or TComponent.Tag. In which case you use whatever you are given. – David Heffernan Sep 13 '13 at 06:14
  • Actually signed pointers are fine. Nothing breaks down when you cross 2^31. Hardware adders operate just the same on signed and unsigned types. – David Heffernan Sep 14 '13 at 23:19
  • And never mind the adders. Usually you cast pointer to int to store it. TComponent.Tag is the canonical example. Simply does not matter what sign the value has so long as the but pattern is preserved. – David Heffernan Sep 15 '13 at 07:40