【问题标题】:Delphi SizeOf(NativeInt) vs C sizeof(int) on x86-64. Why the Size difference?x86-64 上的 Delphi SizeOf(NativeInt) 与 C sizeof(int)。为什么大小差异?
【发布时间】:2023-10-09 00:16:01
【问题描述】:

前言

所以在做了很长一段时间的纯 C 工作后,我回到了 Delphi,发现 Delphi 中有一些新东西。一个是 NativeInt

令我惊讶的是,我发现 Delphi 和 C 处理它们的“本机整数”1 类型对于 x86-64 是不同的。 Delphi NativeInt 似乎表现得像 C void *Delphi Pointer,这与我对名称的期望相反。

Delphi NativeInt 中是 64 位大小。用代码表示:

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

C 只有 64 位指针。 int 仍然是 32 位。用代码表示2

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

即使是 Free Pascal 编译器3 也同意 NativeInt 的大小。

问题

为什么 Delphi NativeInt 选择 64 位而 C int 选择 32 位?

当然,根据语言文档/规范,两者都是有效的。但是,“语言允许”并不是一个真正有用的答案。

我想这与执行速度有关,因为这是当今 C 语言的主要卖点。*和其他来源都说 x86-64 确实有 64 位操作数寄存器。但是,它们还声明默认操作数大小为 32 位。那么,与 32 位操作数相比,对 64 位操作数的操作可能会更慢?或者也许 64 位寄存器可以同时进行 2 个 32 位操作?有这个原因吗?

编译器的创建者选择这些大小可能还有其他原因吗?

脚注

  1. 我将 Delphi NativeIntC int 进行比较,因为名称/规范表明它们具有相似的目的。我知道还有 Delphi Integer,其行为类似于 Delphi 中 x68 和 x86-64 上的 C int
  2. sizeof() 在 C 中返回大小为 char 的倍数。但是,char 在 x86-64 上为 1 个字节。
  3. 它在 Delphi 模式和 NativeInt 的默认模式下这样做。默认模式下的其他整数类型完全是另一回事。

【问题讨论】:

  • NativeInt 可能更像来自<stdint.h> 标准标头(C99 标准)的intptr_t,并且intptr_t 可能大于int(但它与@987654329 大小相同@)
  • SizeOf(Char) = 2 自 2009 年以来。 NativeInt 和 NativeUInt 在 D2009 之前都是错误的。
  • ...这就是为什么我更喜欢 FPC 的PtrInt PtrUInt 语法的原因。这是FPC中的正确方式:FPC在Delphi之前为Win64编译! “NativeInt”仅用于与 Delphi 兼容。 “原生”到底是什么意思? blog.synopse.info/post/2010/08/10/…
  • @LURD 不,RP 的意思是 sizeof(char) 根据定义等于 C 上的 1。即使 char 是 16 位宽,那么 sizeof(char) 也是 1。换句话说, sizeof()char 为单位测量大小。
  • @RotatingPieces,native表示与处理器的位大小一致。即 32 位处理器上的 32 位和 x86-64 上的 64 位。在 128 位处理器上,它仍然是原来的两倍。

标签: c delphi x86-64 pascal sizeof


【解决方案1】:

NativeInt 只是一个与指针大小相同的整数。因此,它会在不同平台上改变大小。 documentation 就是这么说的:

NativeInt 的大小相当于当前平台上指针的大小。

NativeInt 的主要用途是存储诸如操作系统句柄之类的东西,它们在后台实际上是内存地址。您不应该使用它来执行算术运算、存储数组长度等。如果您尝试这样做,那么在您的程序的 32 位和 64 位版本之间共享代码会变得更加困难。

你可以认为 Delphi NativeInt 直接等同于 .net 类型 IntPtr。在 C 和 C++ 中,操作系统句柄类型通常被声明为 void*,它是指针类型而不是整数类型。但是,如果您愿意,您最好使用像 intptr_t 这样的类型。

您使用术语“本机整数”来描述 NativeInt,但尽管名称如此,但重要的是要意识到 NativeInt 不是该语言的本机整数类型。那将是IntegerNativeInt 中的 native 指的是底层硬件平台,而不是语言。

Delphi 类型Integer,语言原生整数,与C 类型int,对应的语言原生类型相匹配。在 Windows 上,这些类型对于 32 位和 64 位系统都是 32 位宽。

当 Windows 设计人员开始开发 64 位 Windows 时,他们对int 在从 16 位系统到 32 位系统的过渡过程中从 16 位更改为 32 位时发生的事情有着深刻的记忆。这一点都不好玩,尽管这显然是一个正确的决定。这一次,从 32 到 64,没有令人信服的理由使 int 成为 64 位类型。如果 Windows 设计人员这样做了,移植工作就会变得更加困难。因此他们选择将int 保留为 32 位类型。

就性能而言,AMD64 架构旨在高效地在 32 位类型上运行。由于 32 位整数是 64 位整数大小的一半,因此通过在 64 位系统上使 int 仅 32 位来减少内存使用量。这将带来性能优势。

几个cmets:

  • 您声明“C 只有 64 位指针”。事实并非如此。 32 位 C 编译器通常会使用带有 32 位指针的平面 32 位内存模型。
  • 您还说,“在 Delphi 中,NativeInt 的大小为 64 位”。又不是这样。它是 32 位或 64 位宽,具体取决于目标。

【讨论】:

  • 你需要小心。对于 Windows 上的 C,int 始终为 32 位。这就是平台标准。但是,尽管没有用,但可以为 int 不是 32 位的 Windows 编写符合标准的 C 编译器。如果我没记错的话,int 必须至少为 16 位宽,但这是标准设置的唯一限制,除了 sizeof(int) >= sizeof(short) 等相对顺序之外。
  • @RotatingPieces 这一切都归结为你所说的原生。在 Delphi 设计者的心目中,这个词意味着“硬件原生”。但是您的推论是“母语”,这在我看来也是完全合理的。无论如何,它就在那里。这就是我们所拥有的。
  • 我写了“你的意思是说更多吗?”响应您不小心按回车时出现的半评论。您现在已经对其进行了编辑。因此我的评论消失了。
  • 同意。我自己会叫它IntPtr
  • 如果不考虑其他平台,就不可能讨论这些。否则你怎么解释NativeIntInt64的存在呢?我相信你已经知道我所说的大部分内容,而且还有很多。但答案不仅适合你,甚至大部分时间都不适合你。这里的答案将持续存在,当有人比你经验少得多时想知道NativeInt 是什么时,他们可能会落在这里。您所需要的只是文档链接。但这并不能为经验不足的程序员提供非常有用的答案。你明白我的意思了吗?
【解决方案2】:

请注意,NativeInt 不是用于与指针交互!

问题是 nativeInt 已签名。
通常这不是你想要的,因为指针指向数据块的开头。 负偏移量会让您在此处违反访问权限。
如果你有一个指向中间的指针(因为你正在做索引或类似的事情),那么负偏移量就会应用,NativeInt aka IntPtr 就会出现。

对于标准指针(指向起点):使用 UIntPtr,因为当偏移量大于 2^31/2^63 时,它不会崩溃。
(可能在 32 位平台上,而不是在 64 位平台上)

因此,有一个 UIntPtr,它完全映射到 C 等效项。
UIntPtrNativeUint

用例
您选择使用哪种类型取决于用例。

A:我想要最快的整数 -> 选择 Int32 aka integer;
B1:我想要一个整数来做指针运算 -> 选择 UIntPtr aka NativeUInt*。
B2:我用指针进行索引 -> 选择 IntPtr aka NativeInt
C:我想要一个大整数,但不希望 Int64 在 X86 上给我带来很大的减速 -> 选择 NativeInt。 D:我想要一个 bigint:选择 Int64。 (但要知道它在 X86 上会很慢)。

*) 如果您想让代码的读者清楚地知道您正在弄乱指针,您显然需要将其命名为UIntPtr

【讨论】:

  • 如果要进行指针运算,请使用指针。如果你想以整数类型存储一个不透明的指针,NativeInt 很好。此外,对于偏移量,您绝对需要一个有符号值。
  • 问题是 nativeInt 是有符号的(指针的负偏移没有意义)。 负值在理论上没有意义。然而,在实践中,这真的很快就会变得非常混乱,你希望他们是这样。我经常在 C 中看到它的类型 size_t 是无符号的。当使用 rev() 或指针算术之类的东西时,你会得到一个签名的 ssize_t。然后你比较了一个 ssize_t 和一个 size_t 来检查你是否访问了很多/很少的内存,编译器开始抱怨比较有符号和没有符号的类型。
  • 否认负数的存在是自虐的。 1和2有什么区别?尝试回答没有负数。为什么我们习惯性地使用有符号整数?为什么 Java 完全省略了有符号值?明信片上的答案.... :-)
  • NativeUInt 是一个无符号指针大小的类型。如果您需要以整数类型存储指针,IMO 这是正确的使用 - 不是NativeInt。 @DavidHeffernan,虽然总的来说我同意并更喜欢有符号类型,因为它更“自然”,但无符号类型有它们的位置,尽管它很少见,尤其是对于地址。 @RotatingPieces,指针 do 的负偏移是有意义的。尝试浏览索引的、自引用的二进制文件或数据结构。
  • @DavidM 不是 RP 说负偏移没有意义。 RP 的评论对此提出了质疑。粗体是引用。至于您对我的评论,如果您需要存储指针,请使用指针。如果您将指针放入整数中,通常是因为其他人决定了类型。例如 Windows 中的 lpData 参数,或 TComponent.Tag。在这种情况下,你可以使用你得到的任何东西。
最近更新 更多