【问题标题】:Why can't we do IntPtr and UIntPtr arithmetic in C#?为什么我们不能在 C# 中进行 IntPtr 和 UIntPtr 算术?
【发布时间】:2011-08-01 04:23:42
【问题描述】:

这是一个看起来很简单的问题:

鉴于原生大小的整数最适合算术,为什么 C#(或任何其他 .NET 语言)不支持原生大小的 IntPtrUIntPtr 的算术?

理想情况下,您可以编写如下代码:

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

这样它就可以在 32 位和 64 位平台上运行。 (目前,您必须使用long。)


编辑:

使用这些作为指针(甚至没有提到“指针”这个词)!它们可以被视为 MSIL 中 native int 和 C 中 stdint.hintptr_t 的 C# 对应物——它们是整数,不是指针。

【问题讨论】:

  • @BoltClock:是的...你认为返回native int的方法的.NET返回类型是什么?

标签: c# .net intptr


【解决方案1】:

因为这不是处理内存寻址的“安全”方式。指针算法可能导致 C# 明确设计要避免的各种错误和内存寻址问题。

【讨论】:

  • 这不是一回事。 C 不是 .NET。 .NET 是一个托管环境。在托管世界中,指针不是固定值。您有 references 来保存 .NET 框架用来在内存中定位对象的值。 GC 可以随时移动它们的实际位置。这就是为什么指针不安全并且很难使用它们的原因。
  • @Paul:这与指针无关!您是否熟悉 MSIL 的 native int 数据类型?它与void* 完全不同...
  • 是的,我非常熟悉 JIT 的内部工作原理以及它实际生成代码的方式。 IntPtr 是一种用于保存指针特殊 类型。您正试图将其用于其他用途。 IntPtr 类型旨在为托管环境提供一种安全的方式来保存指针。它不是任意整数值。
  • @Paul:当一个方法在 IL 中返回 native int 时,您在反射时看到的返回类型的数据类型是什么?还有native int和指针有什么关系?
  • 这与您的问题有什么关系?无论您返回一个 int、一个 ref、一个 InPtr 还是一个原生 int,当它被 JITted 时,它都存储在 eax/rax 中 - 让原生大小的寄存器感到惊讶。
【解决方案2】:

在 .NET 4 中,支持IntPtr 类型的左侧操作数和整数类型(intlong 等)的右侧操作数之间的算术运算。 p>

[编辑]: 正如其他人所说,它们旨在表示本地语言中的指针(正如名称 IntPtr 所暗示的那样)。可以声称您将它们用作本机整数而不是指针,但您不能忽视整数的本机大小一直很重要的主要原因之一是用作指针。如果您正在执行数学运算或其他独立于您的代码运行的处理器和内存体系结构的通用功能,则可以说在您知道的地方使用intlong 等类型更有用和更直观无论硬件如何,它们在每种情况下都有固定的大小和上限和下限。

正如IntPtr 类型被设计用于表示本地指针一样,算术运算旨在表示您将对指针执行的逻辑数学运算:将一些整数偏移量添加到本地指针以到达新的本地指针(不是不​​支持添加两个IntPtrs,也不支持使用IntPtr作为右手操作数)。

【讨论】:

  • +1 我不知道! =O 但我的意思是完全使用IntPtr,而不是在混合中加入另一个固定大小的整数。
  • 更新了我对为什么使用固定大小的整数适合这种情况的想法
  • @jeffora:所以你基本上是说这在任何情况下都没有用,我理解;正确的?虽然我觉得很难下咽,但这似乎是一个非常合理的解释。 :)
  • 如果我错了,请纠正我,但 size_t 应该代表对象的大小,其大小取决于平台。 IntPtr 是本机大小的整数类型,但根据 MSDN,它是“用于表示指针或句柄的特定于平台的类型”。 (msdn.microsoft.com/en-us/library/system.intptr.aspx),即指针或句柄,而不仅仅是任意平台大小的 int
  • 当然,您可以将它用作任意平台大小的 int,但可能不要期望它支持对其设计的特定表示没有意义的操作。
【解决方案3】:

也许原生大小的整数是最快算术,但它们肯定不适合最无错误的程序。

就我个人而言,我讨厌用我不知道什么时候开始打字的整数类型进行编程(我在看着你,C++),而且我绝对更喜欢内心的平静CLR 类型为您提供了使用为平台量身定制的 CPU 指令可能提供的非常可疑且肯定有条件的性能优势。

还请考虑 JIT 编译器可以针对运行进程的体系结构进行优化,而“常规”编译器必须生成机器代码而无法访问此信息。因此,JIT 编译器可能会以同样快的速度生成代码,因为它知道的更多。

我想我不是唯一一个这样想的人,所以这可能是有原因的。

【讨论】:

  • 现在 这是 一个合理的解释(与完全不相关的响应“指针不安全!”相反)。 :) 所以现在我要问你的问题是,你应该如何与其中包含 size_t 字段的结构进行互操作,该结构包含某个数组的索引? (对于 C++ -bash 的 +1...)
  • @Mehrdad:首先,我刚刚添加了第三段;也读一读。
  • 我现在阅读了您的第三段,但这不是只是一个效率问题:它是一个正确性问题,例如,迭代数组可以具有任意大小,以独立于平台的方式。
  • @Mehrdad:关于size_t:我对互操作内部机制了解不多。如果您必须将size_t 映射到相同大小的字段,那么在一般情况下技术上互操作是不可能的,因为大小未知;您只能与特定的二进制文件进行互操作,其中size_t 的大小已由编译器硬编码。如果编组仅是单向的,并且编组器可以将较小的类型存储到较大的类型中,那么您可以将 size_t 编组为 unsigned long 并在我们获得 128 位架构之前没问题。
  • 没错——但是你会死在 128 位架构上。这不正是我们迁移到 64 位时 32 位代码遇到的问题吗?
【解决方案4】:

.Net Framework 尽量不引入无法解释的操作。 IE。没有 DateTime + DateTime 因为没有两个日期之和这样的概念。相同的推理适用于指针类型 - 没有 2 个指针之和的概念。 IntPtr 被存储为平台相关的 int 值这一事实并不重要 - 还有很多其他类型在内部存储为基本值(再次,DateTime 可以表示为 long)。

【讨论】:

  • 与您提到的相反,它实际上不是“存储”为独立于平台的整数(它存储为void*),但它是 一个独立于平台的整数,因为它是 native int 从 MSIL 映射到的内容。我不是说存储机制,只说语义。
【解决方案5】:

我实际上可以想到 IntPtr(或 UIntPtr)有用的一个原因:访问数组的元素需要原生大小的整数。尽管原生整数从未暴露给程序员,但它们在 IL 内部使用。像 C# 中的 some_array[index] 这样的东西实际上会编译成 IL 中的 some_array[(int)checked((IntPtr)index)]。在使用 ILSpy 反汇编 我自己的 代码后,我注意到了这一点。 (index 变量在我的代码中是 64 位的。)为了验证反汇编程序没有出错,Microsoft 自己的 ILDASM 工具显示我的程序集中存在 conv.uconv.i 指令。这些指令将整数转换为系统的本机表示。我不知道在 IL 代码中包含所有这些转换指令对性能的影响是什么,但希望 JIT 足够聪明,可以优化性能损失;如果不是,那么下一个最好的办法是允许在不进行转换的情况下操作本机整数(在我看来,这可能是使用本机类型的主要动机)。

目前,F# 语言允许使用 nativeint 及其无符号对应物进行算术运算。但是,在 F# 中,数组只能由 int 索引,这意味着 nativeint 对于索引数组的目的不是很有用。

如果它真的让您很困扰,请编写您自己的编译器来解除对本地整数使用的限制,创建您自己的语言,在 IL 中编写您的代码,或者在编译后调整 IL。就个人而言,我认为通过使用本机 int 来挤出额外的性能或节省内存是一个坏主意。如果您希望您的代码像手套一样适合系统,您最好使用支持处理器内部函数的低级语言。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-22
    • 2012-06-10
    相关资源
    最近更新 更多