【问题标题】:Ref - Parameters - Stack or HeapRef - 参数 - 堆栈或堆
【发布时间】:2014-04-16 16:17:45
【问题描述】:

这只是一个理论问题,但我无法得到一个好的答案:

如果我通过ref 传递参数,则传递对象本身而不是副本。

这让我感到困惑:据我所知,每个方法都有自己的堆栈帧 - 内存,它们无法离开。那么这是否意味着 ref - Object 被打包在堆上并且有对该参数的引用,或者该方法是否进入调用方法的堆栈并在那里工作?

如果我的问题令人困惑,我很抱歉,我基本上想知道如何保存 ref 类型以及它有什么影响。

伊迪丝: 我想我没有说清楚。我理解值和引用类型的概念。为了方便起见,我试着用一个值类型来解释它,比如说 Int:

过程 1 通过传递一个 Int ByVal 调用 Prodecure 2。这个 int 在 Prodecure 2 的 Stack 上有自己的内存,这意味着,改变 P2 中的这个值不会改变 P1 中的值,因为这 2 个值在每个 Stack 中保存一次。

现在与 byref 相同:Prodecure 2 不保存 Int 的副本,但可以直接访问该值。 (在我看来)有两种可能使这项工作:

  1. int 被打包在堆上,实际上有 2 个指针 这个 Int,但由于它现在在堆上,因此可以看到值的变化 在这两个 Prodecures 上。
  2. P2 可以通过某种方式访问​​ P1 的堆栈,这 我想,这是不可能的,因为这意味着, Stone 中未设置堆栈生命周期。

这是否更清楚我的意思?

【问题讨论】:

  • 这可能对你有帮助:yoda.arachsys.com/csharp/parameters.html
  • 对象本身没有被传递,对象的引用被传递。按价值。在堆栈上或在寄存器中或通过其他一些实现细节。
  • @Rahul 不,实际上。当您通过ref 传递引用时,您传递的是对引用的引用。这类似于指向指针的指针。您没有传递相同引用的副本,而是按值传递引用。
  • @Servy,不清楚。比如说,我的对象obj 在堆地址0xxab 中。那么当你通过 ref 时,你实际上传递了相同的地址.. 对吗? (或)它通过另一个引用说0xxdm,它指向0xxab
  • @RoyiNamir 是的。正如我从一开始就说过的,通过引用传递引用是将引用传递给存储引用的位置,而不是复制引用的值。当您按值传递引用时,您会复制引用的值。

标签: c#


【解决方案1】:

传递的参数是某个对象的地址。该引用与方法的所有其他参数一起在堆栈上传递。

在调用方法之前,实际对象本身就存在于它所在的任何地方。那可能在堆栈中,也可能在堆中,没关系。通过引用传递对象的行为不会导致它在内存中移动,例如从堆栈移动到堆,或从堆移动到堆栈。

【讨论】:

  • 我的印象是对象可以在内存中移动(除非固定),并且引用会以某种方式跟随(直接更新或双重间接)。否则,一旦对象的引用存在,GC 将很难完成其工作。
  • @FrédéricHamidi 这讲的是一个有点不同的抽象层。 (从任何引用堆中某物的对象的角度来看,它根本不会移动,因为它们的所有引用总是去同一个地方。GC 命中不同的内存块来解析地址在不同的黑盒子内。)在任何情况下,由于对象是通过引用传递而没有移动的,这是一个移动的对象,因为 GC 正在进行收集。我的主要观点是,与闭包不同,变量在通过ref 传递时不会被提升。
  • 我相信除非我们用fixed 声明明确地固定它。但是通过fixed,我们确保在 GC 工作时对象应该保持在原处(不可移动)。否则,只需传递引用;是的,物体不会移动到任何地方。
  • @Servy,我明白了。我确实未能从您的回答中推断出这一点,也许提及捕获有助于使 move 的意思更清楚。
  • @RoyiNamir 我从来没有说过它没有。我说通过引用传递它的行为不会移动它,并不是说它不能被一些完全独立的效果移动它,例如 GC 压缩。问题是暗示堆栈上的对象将通过引用传递的行为移动到堆中。事实并非如此,它会留在任何地方。
【解决方案2】:

尽管 Servy 已经正确回答了这个问题,但对于使用 ref 传递参数和通过值传递对象的引用之间的区别似乎存在很多混淆。因此,我认为值得提供一个简短的说明。

假设以下简单类:

class Player
{
    public Player(int health)
    {
        Health = health;
    }
    public int Health { get; set; }
}

我们现在可以测试更新对象的属性并更改引用本身:

static void Main(string[] args)
{
    Player player = new Player(100);
    Console.WriteLine(player.Health);

    ChangeHealth(player);
    Console.WriteLine(player.Health);
    ChangeHealthByRef(ref player);
    Console.WriteLine(player.Health);

    ChangePlayer(player);
    Console.WriteLine(player.Health);
    ChangePlayerByRef(ref player);
    Console.WriteLine(player.Health);
}

static void ChangeHealth(Player player)
{
    player.Health = 80;
}

static void ChangeHealthByRef(ref Player player)
{
    player.Health = 60;
}

static void ChangePlayer(Player player)
{
    player = new Player(40);
}

static void ChangePlayerByRef(ref Player player)
{
    player = new Player(20);
}

输出:

100
80
60
60
20

ChangeHealth 成功修改了player 对象的Health 属性。 ChangeHealthByRef 也成功修改了player 对象的Health 属性。所以你可以看到,在这两个调用中,player 所引用的对象可以被修改,尽管 ChangeHealth 使用引用的副本。

现在,这是我认为人们感到困惑的部分:

ChangePlayer 创建一个新的Player 对象,该对象修改传入的引用的副本。这意味着更改不会反映在调用代码中(即Health still = 60)。 ChangePlayerByRef 也创建了一个新的 Player 对象,但是这一次,它直接修改引用,这意味着更改确实反映在调用代码中(即@987654338 @ = 20)。

【讨论】:

    【解决方案3】:

    看看这个问题的答案。

    Do int ref parameter get boxed?

    也许它可以帮助你。

    谢谢

    【讨论】:

    • 这应该是一个评论:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-31
    • 2014-06-23
    • 2013-05-10
    • 2011-07-22
    • 2013-12-06
    • 2017-03-25
    • 2018-01-06
    相关资源
    最近更新 更多