【问题标题】:Memory usage of pass by value vs. pass by reference按值传递与按引用传递的内存使用情况
【发布时间】:2020-01-09 23:53:14
【问题描述】:

在过去的几天里,我试图了解按值传递和按引用传递对内存的影响是否不同。谷歌搜索这个查询,人们不断重复自己关于按值传递创建的副本以及原始值如何在按引用传递方面受到影响。但我想知道是否有人可以将内存部分归零。

【问题讨论】:

  • 什么编程语言?
  • 我在考虑 C++ 和 JAVA

标签: memory pass-by-reference pass-by-value


【解决方案1】:

这个问题实际上在很大程度上取决于特定的语言,因为有些语言允许您明确定义何时希望通过值传递变量以及何时通过引用传递变量,而有些则对于不同类型的变量总是以相同的方式进行。

一种非常流行的行为类型是在简单的时候使用按值传递(默认情况下):如 int、string、long、float、double、bool 等。

让我们展示内存对理论语言的影响:

int $myVariable = 5;

此时您已经在内存中创建了一个变量,它采用存储整数所需的大小(假设为 32 位)。

现在你想把它传递给一个函数:

function someFunction(int parameter)
{
    printOnScreen(parameter);
}

所以你的代码看起来像:

function someFunction(int $parameter)
{
    printOnScreen($parameter);
}

int $myVariable = 5; //Position A
someFunction($myVariable); //Position B
...rest of the code //Position C

由于简单类型是按值传递的,因此值会在内存中复制到另一个存储位置 - 因此:

在位置 A 期间,您的内存被 ONE int(值为 5)占用; 在位置 B 期间,您的内存被两个整数(值为 5)占用,因为您的 $myVariable 被复制到内存中 在位置 C 期间,您再次占用了 ONE int(值为 5)的内存,因为第二个 int 已被销毁,因为它仅在执行函数时需要

这还有一些其他含义:对按值传递的变量的修改不会影响原始变量 - 例如:

function someFunction(int $parameter)
{
    $parameter = $parameter + 1;
    printOnScreen($parameter);
}

int $myVariable = 5; //Position A
someFunction($myVariable); //Position B
printOnScreen($myVariable); //Position C

在位置 A 期间,您将变量 $myVariable 下的值设置为 5。 在位置 B 期间,您将 BY VALUE 传递给一个函数,该函数将您传递的值加 1。然而,因为它是一个简单的类型,通过值传递,它实际上对一个 LOCAL 变量进行操作,一个变量的副本。因此位置 C 将再次只写 5(您的原始变量,因为它没有被修改)。

某些语言允许您使用特殊运算符明确并告知您要传递引用而不是值本身 - 例如&。因此,让我们再次遵循相同的示例,但带有我们想要引用的明确信息(在函数的参数中 - 注意&):

function someFunction(int &$parameter)
{
    $parameter = $parameter + 1;
    printOnScreen($parameter);
}

int $myVariable = 5; //Position A
someFunction($myVariable); //Position B
printOnScreen($myVariable); //Position C

这一次的操作和内存影响会有所不同。

在位置 A 期间,创建了一个 int(每个变量始终由两个元素组成:内存中的位置和一个指针,它是位置的标识符。为了便于处理,让我们说指针始终是一个字节)。因此,每当您创建一个变量时,您实际上会创建两件事:

  • 在内存中为 VALUE 保留位置(在本例中为 32 位,因为它是一个 int)
  • 指针(8 位 [1 字节])

现在在位置 B 期间,函数期望 A POINTER 指向内存位置。这意味着它将在本地为自己创建指针的副本(1 个字节),而不是将实际保留位置作为新指针将指向与原始位置相同的位置。这意味着在功能运行期间,您拥有:

两个指向内存中的 int 的指针 为 int 的 VALUE 保留一个位置 这两个指针都指向同一个 VALUE

这意味着对值的任何修改都会影响两者。

因此查看相同的示例位置 C 也不会打印出 6,因为在函数内部我们已将 SAME POINTER 下的值修改为 $myVariable。

对于复杂类型(对象),大多数编程环境中的默认操作是传递引用(指针)。

例如 - 如果你有一个班级:

class Person {
   public string $name;
}

并创建它的一个实例并设置一个值:

$john = new Person();
$john->name = "John Malkovic";

然后将其传递给函数:

function printName(Person $instanceOfPerson) 
{
   printOnScreen($instanceOfPerson);
}

就内存而言,它将再次在内存(1 字节)中创建一个指向相同值的新指针。所以有这样的代码:

function printName(Person $instanceOfPerson) 
{
   printOnScreen($instanceOfPerson);
}

$john = new Person(); // position A
printName($john); // position B
...rest of the code // position C

在位置 A 期间,您有:1 个人(这意味着 1 个指针 [1 字节] 指向内存中的某个位置,该位置具有存储人员类对象的大小)

在位置 B 期间,您有:2 个指针 [2 个字节],但在内存中仍然有一个位置来存储类 person 的值 [instance] 的对象

在位置 C 期间,您再次遇到位置 A 的情况

我希望这可以为您阐明主题 - 通常还有更多内容要介绍,我上面提到的只是一般解释。

【讨论】:

  • 伙计,我非常感谢您的回答。这个解决方案是选择。但我有一个简短的问题要问你。 - 当您说每当您创建一个变量时,您是什么意思,您会在内存中创建两个空间和一个指针。嗯,那不是一个链表吗?
【解决方案2】:

按值传递和按引用传递是语言语义概念;他们并不暗示任何关于实施的事情。通常,具有按引用传递的语言通过按值传递指针来实现它,然后当您读取或写入函数内部的变量时,编译器会将其转换为从指针的取消引用中读取或写入。因此,您可以想象,例如,如果您有一个在 C++ 中通过引用获取参数的函数:

struct Foo { int x; }
void bar(Foo &f) {
    f.x = 42;
}
Foo a;
bar(a);

它真的是语法糖:

struct Foo { int x; }
void bar(Foo *f_ptr) {
    (*f_ptr).x = 42;
}
Foo a;
bar(&a);

因此通过引用传递与通过值传递指针具有相同的成本,这确实涉及“副本”,但它是指针的副本,它是几个字节,无论指向的事物的大小如何.

当您谈论通过值传递执行“复制”时,除非您知道传递的变量或值在语言中究竟代表什么,否则这并不能真正告诉您太多。例如,Java 只有传值。但是Java中的每种类型要么是原始类型,要么是引用类型,并且引用类型的值是“引用”,即指向对象的指针。因此,您永远不能在 Java 中拥有一个“是”“对象”的值(变量包含什么或表达式的计算结果); Java 中的对象只能通过这些“引用”(指向对象的指针)进行操作。因此,当您询问在 Java 中传递对象的成本时,实际上是错误的,因为您无法在 Java 中“传递”对象;您只能传递引用(指向对象的指针),而按值传递发生的副本是指针的副本,即几个字节。

因此,您在传递时实际上会复制一个大结构的唯一情况是,如果您有一种语言,其中对象或结构直接是值(而不是在引用后面),并且您对该对象进行按引用传递/结构类型。例如,在 C++ 中,您可以拥有直接作为值的对象,或者您可以拥有指向它们的指针,您可以通过值或引用传递它们:

struct Foo { int x; }
void bar1(Foo f1) { } // pass Foo by value; this copies the entire size of Foo
void bar2(Foo *f2) { } // pass pointer by value; this copies the size of a pointer
void bar3(Foo &f3) { } // pass Foo by reference; this copies the size of a pointer
void bar4(Foo *&f4) { } // pass pointer by reference; this copies the size of a pointer

(当然,每一个都有不同的语义含义;例如,最后一个允许函数内部的代码修改传递的指针变量以指向其他地方。但是如果您关心复制的数量。只有第一个是不同的。在 Java 中,实际上只有第二个是可能的。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-12
    • 1970-01-01
    • 2023-03-02
    • 2012-07-03
    • 1970-01-01
    • 2016-10-22
    相关资源
    最近更新 更多