Использование памяти передачи по значению против передачи по ссылке - PullRequest
0 голосов
/ 10 января 2020

В течение последних нескольких дней я пытаюсь понять, как передача по значению и передача по ссылке влияют на память по-разному. Погугляя этот запрос, люди повторяли о том, что копия создается с точки зрения передачи по значению и как на исходное значение влияют термины, передаваемые по ссылке. Но мне было интересно, может ли кто-то сосредоточиться на части памяти.

Ответы [ 2 ]

0 голосов
/ 12 января 2020

Передача по значению и передача по ссылке являются понятиями семантики языка; они ничего не подразумевают в реализации. Обычно языки с передачей по ссылке реализуют это, передавая указатель по значению, а затем, когда вы читаете или записываете переменную внутри функции, компилятор переводит ее в чтение или запись по разыменованию указателя. Например, вы можете представить, что если у вас есть функция, которая принимает параметр по ссылке в C ++:

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

, то это действительно синтактический c сахар для чего-то вроде:

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

(Конечно, каждый из они имеют разные значения semanti c, например, последний позволяет коду внутри функции изменять переменную-указатель, передаваемую для указания куда-то еще, но если вас беспокоит количество копий. Только первый отличается. В Java фактически возможен только второй.)

0 голосов
/ 10 января 2020

Этот вопрос на самом деле сильно зависит от конкретного языка, поскольку некоторые позволяют вам быть явными и определять, когда вы хотите передать переменную по значению, а когда по ссылке, а некоторые делают это всегда одинаково для разных типов переменных.

Довольно популярным типом поведения является использование передачи по значению (по умолчанию) для простых времен: таких как int, string, long, float, double, bool et c.

Давайте покажем влияние памяти на теоретический язык:

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), поскольку второй был уже уничтожен поскольку это было необходимо только на время выполнения функции

Это имеет некоторые другие последствия: изменения в переменной, передаваемой по значению, НЕ влияют на исходную переменную - например:

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

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

Во время положения A вы устанавливаете значение 5 в переменной $ myVariable. Во время положения B вы передаете его BY VALUE функции, которая добавляет 1 к вашему переданному значению. ДА, так как это был простой тип, передаваемый по значению, он фактически работает с локальной переменной, копией вашей переменной. Поэтому позиция 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 бита, как это было целым числом)
  • указатель (8 битов) [1 байт])

Теперь в положении B функция ожидает указатель A на место в памяти. Это означает, что он будет локально создавать для себя только копию указателя (1 байт) и не будет копировать фактическое зарезервированное место в качестве нового указателя WILLL POINT в то же место, что и исходный. Это означает, что во время работы функции у вас есть:

ДВА указателя на int в памяти. ОДНО место, зарезервированное для VALUE для int. Оба этих указателя POINT указывают на одно и то же VALUE

Что означает, что любая модификация значения будет влиять на оба.

Поэтому, глядя на ту же позицию примера C не будет выводить также 6, поскольку внутри функции, которую мы изменили значение в ОДНОМ ЖЕ УКАЗАТЕЛЕ, как $ 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 байт] на место в памяти, размер которого позволяет хранить объект класса person)

во время позиции B у вас есть: 2 указателя [2 байта], но ЕЩЕ ОДНО место в памяти для хранения объекта значения класса [экземпляр]

во время позиции C у вас снова ситуация из позиции A

Я надеюсь, что это проясняет для вас топи c - как правило, есть еще кое-что, и то, что я упомянул выше, является лишь общим объяснением.

...