【问题标题】:What is the difference between a C# Reference and a Pointer?C# 引用和指针有什么区别?
【发布时间】:2010-09-30 14:46:19
【问题描述】:

我不太明白 C# 引用和指针之间的区别。他们都指向记忆中的一个地方,不是吗?我能弄清楚的唯一区别是指针不那么聪明,不能指向堆上的任何东西,免于垃圾收集,并且只能引用结构或基类型。

我问的一个原因是,人们认为人们需要很好地理解指针(我猜是从 C 语言)才能成为一名优秀的程序员。许多学习高级语言的人错过了这一点,因此有这个弱点。

我只是不明白指针有什么复杂之处?它基本上只是对内存中某个位置的引用,不是吗?它可以返回它的位置并直接与该位置的对象交互吗?

我错过了重要的一点吗?

【问题讨论】:

  • 简短的回答是肯定的,你错过了一些相当重要的东西,这就是“......人们需要理解指针的看法”的原因。提示:C# 并不是唯一的语言。

标签: c# pointers reference


【解决方案1】:

指针和引用之间存在细微但极其重要的区别。指针指向内存中的位置,而引用指向内存中的对象。指针不是“类型安全的”,因为您无法保证它们指向的内存的正确性。

以下面的代码为例

int* p1 = GetAPointer();

这是类型安全的,因为 GetAPointer 必须返回与 int* 兼容的类型。然而,仍然不能保证 *p1 实际上会指向一个 int。它可以是 char、double 或只是指向随机内存的指针。

然而,一个引用指向一个特定的对象。对象可以在内存中移动,但引用不能无效(除非您使用不安全的代码)。在这方面,引用比指针安全得多。

string str = GetAString();

在这种情况下 str 具有两种状态之一:1)它不指向任何对象,因此为 null 或 2)它指向一个有效的字符串。而已。 CLR 保证是这种情况。它不能也不会用于指针。

【讨论】:

    【解决方案2】:

    C# 引用可以并且将被垃圾收集器重定位,但普通指针是静态的。这就是为什么我们在获取指向数组元素的指针时使用fixed关键字,以防止它被移动。

    编辑:从概念上讲,是的。它们或多或少是一样的。

    【讨论】:

    • 难道没有另一个命令可以阻止 C# 引用使其引用的对象被 GC 移动吗?
    • 哦,对不起,我以为是别的东西,因为帖子提到了一个指针。
    • 是的,GCHandle.Alloc 或 Marshal.AllocHGlobal(已修复)
    • 在 C# 中是固定的,在 C++/CLI 中是 pin_ptr
    • Marshal.AllocHGlobal 根本不会在托管堆中分配内存,自然不会受到垃圾回收的影响。
    【解决方案3】:

    引用是一个“抽象”指针:你不能用引用做算术,你不能用它的值玩任何低级技巧。

    【讨论】:

      【解决方案4】:

      引用和指针之间的主要区别在于,指针是位的集合,其内容仅在主动用作指针时才重要,而引用不仅封装了一组位,还封装了一些元数据让底层框架知道它的存在。如果存在指向内存中某个对象的指针,并且该对象被删除但指针没有被擦除,则指针的继续存在不会造成任何伤害,除非或直到尝试访问它指向的内存。如果不尝试使用指针,则不会关心它的存在。相比之下,.NET 或 JVM 等基于引用的框架要求系统始终可以识别存在的每个对象引用,并且存在的每个对象引用必须始终是 null 或识别其对象正确的类型。

      请注意,每个对象引用实际上封装了两种信息:(1) 它标识的对象的字段内容,以及 (2) 对同一对象的其他引用的集合。尽管没有任何机制可以让系统快速识别对象的所有引用,但对象的其他引用集通常可能是引用封装的最重要的东西(尤其是在Object 类型的东西用作锁定令牌之类的东西)。尽管系统为每个对象保留了一些数据位以供在GetHashCode 中使用,但除了存在于它们的引用集之外,对象没有真正的身份。如果X 拥有对一个对象的唯一现存引用,则将X 替换为对具有相同字段内容的新对象的引用将不会产生任何可识别的效果,除非更改GetHashCode() 返回的位,甚至这种效果也是'不保证。

      【讨论】:

        【解决方案5】:

        指针指向内存地址空间中的一个位置。引用指向一个数据结构。垃圾收集器(用于压缩内存空间)一直在移动数据结构(嗯,不是那么频繁,但时不时地)。此外,正如您所说,没有引用的数据结构会在一段时间后被垃圾收集。

        另外,指针只能在不安全的上下文中使用。

        【讨论】:

          【解决方案6】:

          我认为理解指针的概念对开发人员来说很重要——即理解间接性。这并不意味着他们必须使用指针。同样重要的是要了解引用的概念指针的概念不同,虽然只是细微的差别,但引用的实现几乎总是是 一个指针。

          也就是说,持有引用的变量只是持有指向对象的指针的指针大小的内存块。但是,不能像使用指针变量那样使用该变量。在 C#(和 C,和 C++,...)中,指针可以像数组一样被索引,但引用不能。在 C# 中,垃圾收集器会跟踪引用,而指针则不能。在 C++ 中,可以重新分配指针,而不能重新分配引用。在语法和语义上,指针和引用完全不同,但在机制上,它们是相同的。

          【讨论】:

          • 数组听起来很有趣,基本上你可以告诉指针像数组一样偏移内存位置,而你无法通过引用来做到这一点?想不出什么时候会有用,但仍然很有趣。
          • 如果 p 是一个 int*(指向一个 int 的指针),那么 (p + 1) 是由 p + 4 个字节(一个 int 的大小)标识的地址。 p[1] 与 *(p + 1) 相同(即,它“取消引用”地址 4 个字节后 p)。相比之下,使用数组引用(在 C# 中),[] 运算符执行函数调用。
          【解决方案7】:

          首先,我认为您需要在语义中定义一个“指针”。你的意思是你可以用fixed在不安全的代码中创建指针吗?你的意思是一个IntPtr,你可能来自一个本地电话或Marshal.AllocHGlobal?你的意思是GCHandle?所有这些本质上都是相同的东西——存储某物的内存地址的表示——无论是类、数字、结构,等等。并且为了记录,它们当然可以在堆上。

          指针(以上所有版本)是固定项。 GC 不知道该地址是什么,因此无法管理对象的内存或生命周期。这意味着您将失去垃圾收集系统的所有好处。您必须手动管理对象内存,并且存在泄漏的可能性。

          另一方面,引用几乎是 GC 知道的“托管指针”。它仍然是一个对象的地址,但现在 GC 知道目标的详细信息,因此它可以移动它、执行压缩、完成、处置以及托管环境所做的所有其他好事情。

          真正的主要区别在于您使用它们的方式和原因。对于托管语言中的绝大多数情况,您将使用对象引用。指针对于进行互操作和极少需要非常快速的工作变得很方便。

          编辑:事实上here's a good example 什么时候可以在托管代码中使用“指针” - 在这种情况下它是一个 GCHandle,但完全相同的事情可以用 AllocHGlobal 或通过在字节数组或结构上使用固定。我更喜欢 GCHandle,因为我觉得它更像“.NET”。

          【讨论】:

          • 一个小问题,也许你不应该在这里说“托管指针”——即使是吓人的引号——因为这与 IL 中的对象引用完全不同。尽管 C++/CLI 中有托管指针的语法,但它们通常无法从 C# 访问。在 IL 中,它们是通过(即)ldloca 和 ldarga 指令获得的。
          【解决方案8】:

          指针可以指向应用程序地址空间中的任何字节。 引用受到 .NET 环境的严格约束、控制和管理。

          【讨论】:

            【解决方案9】:

            引用相对于指针的最大好处之一是更简单和可读性更高。与往常一样,当您简化某些内容时,您会使其更易于使用,但会以灵活性和控制权为代价(正如其他人提到的那样)。

            指针经常被批评为“丑陋”。

            class* myClass = new class();
            

            现在每次使用它时都必须先取消引用它

            myClass->Method() or (*myClass).Method()
            

            尽管失去了一些可读性并增加了复杂性,但人们仍然需要经常使用指针作为参数,这样您就可以修改实际对象(而不是通过值传递)并获得不必复制巨大对象的性能。

            对我来说,这就是为什么引用首先“诞生”以提供与指针相同的好处,但没有所有指针语法。现在您可以传递实际的对象(而不仅仅是它的值),并且您可以通过一种更易读、更正常的方式与对象进行交互。

            MyMethod(&type parameter)
            {
               parameter.DoThis()
               parameter.DoThat()
            }
            

            C++ 引用与 C# / Java 引用的不同之处在于,一旦您为它分配了一个值,就不能重新分配它(并且必须在声明它时分配它)。这与使用 const 指针(不能重新指向另一个对象的指针)相同。

            Java 和 C# 是非常高级的现代语言,它们清理了多年来在 C / C++ 中积累的大量混乱,而指针绝对是需要“清理”的东西之一。

            就您关于知道指针的评论使您成为更强大的程序员而言,这在大多数情况下都是正确的。如果您知道某些东西的“工作原理”,而不是在不知道的情况下使用它,我会说这通常会给您带来优势。有多少优势总是会有所不同。毕竟,在不知道如何实现的情况下使用某些东西是 OOP 和接口的众多优点之一。

            在这个具体的例子中,知道指针对你的引用有什么帮助?理解 C# 引用不是对象本身而是指向对象是一个非常重要的概念。

            #1:你没有传递价值 对于初学者来说,当您使用指针时,您知道指针仅包含一个地址,仅此而已。变量本身几乎是空的,这就是为什么它作为参数传递非常好。除了性能提升之外,您正在使用实际对象,因此您所做的任何更改都不是临时的

            #2:多态性/接口 当您有一个接口类型的引用并且它指向一个对象时,您只能调用该接口的方法,即使该对象可能具有更多功能。对象也可能以不同的方式实现相同的方法。

            如果你很好地理解了这些概念,那么我认为你不会因为没有使用指针而错过太多。 C++ 经常被用作学习编程的语言,因为有时候弄脏你的手是件好事。此外,使用较低级别的方面可以让您欣赏现代语言的舒适性。我从 C++ 开始,现在是 C# 程序员,我确实觉得使用原始指针帮助我更好地理解了底层发生的事情。

            我认为每个人都没有必要从指针开始,但重要的是他们了解为什么使用引用而不是值类型,而理解这一点的最佳方法是查看它的祖先,即指针。

            【讨论】:

            • 就个人而言,我认为如果大多数使用. 的地方都使用->,C# 会是一种更好的语言,但foo.bar(123) 是调用静态方法fooClass.bar(ref foo, 123) 的同义词。那将允许像myString.Append("George")这样的东西; [这将修改 变量 myString],并使 myStruct.field = 3;myClassObject->field = 3; 之间的含义差异更加明显。
            【解决方案10】:

            使指针变得有些复杂的不是它们是什么,而是你可以用它们做什么。而当你有一个指向指针的指针时。这才是真正开始变得有趣的时候。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-10-12
              • 1970-01-01
              • 2018-12-15
              • 1970-01-01
              • 2011-04-04
              • 2010-12-17
              • 2013-07-12
              • 2012-01-13
              相关资源
              最近更新 更多