В чем разница между тем, как ссылки и Box <T>представлены в памяти? - PullRequest
12 голосов
/ 24 января 2020

Я пытаюсь понять, как работают ссылки и Box<T>. Давайте рассмотрим пример кода:

fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

В моем воображении Rust сохраняет значение в памяти как:

enter image description here

Учтите это второй фрагмент кода с Box<T>:

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

Как x будет храниться в Box? Как выглядит память?

Приведенные выше примеры взяты из Обработка интеллектуальных указателей как регулярных ссылок с Deref Чертой . Для второго примера книга объясняет это так:

Единственное отличие между листингом 15-7 и листингом 15-6 состоит в том, что здесь мы устанавливаем y как экземпляр поля, указывающего на значение в x, а не ссылка, указывающая на значение x.

Означает ли это, что y в поле указывает непосредственно на значение 5?

Ответы [ 2 ]

16 голосов
/ 24 января 2020

Ваша диаграмма для простого случая в порядке, хотя она может быть неясной, так как вы используете 5 как для значения, так и для адреса. Я переместил y на своей диаграмме, чтобы избежать путаницы.

Как выглядит память для Box<T>?

Эквивалентная диаграмма для Box будет выглядеть аналогично, но с добавлением кучи:

    Stack

     ADDR                    VALUE
    +------------------------------+
x = |0x0001|                     5 |
y = |0x0002|                0xFF01 |
    |0x0003|                       |
    |0x0004|                       |
    |0x0005|                       |
    +------------------------------+

    Heap

     ADDR                    VALUE
    +------------------------------+
    |0xFF01|                     5 |
    |0xFF02|                       |
    |0xFF03|                       |
    |0xFF04|                       |
    |0xFF05|                       |
    +------------------------------+

(см. примечания pedanti c ниже об этой диаграмме)

Box выделил нам достаточно места в куче, здесь по адресу адрес 0xFF01. Затем значение перемещается из стека в кучу.

Означает ли это, что y в поле указывает непосредственно

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

Смысл главы, которую вы читаете, заключается в том, что Rust будет прозрачно разыменовывать Box для вас, так что вам обычно не нужно беспокоиться об этом факте.

См. также:

Какая разница в памяти?

Это может немного сломать ваш мозг!

Глядя на стек для обоих примеров, есть isn ' t действительно разница между двумя случаями - и ссылка, и Box хранятся в стеке как указатель. Единственная разница - в коде , где он знает, как обрабатывать значение в стеке по-разному, в зависимости от того, является ли это ссылкой или Box.

Фактически, это верно для всего в ржавчине! Для компьютера это всего лишь биты, и структура, закодированная в двоичном файле программы, - это единственное, что отличает один блок байтов от другого.

Почему x остается в стеке после перемещения в Box?

Внимательные читатели заметят, что я оставил значение 5 для x в стеке. Есть две важные причины:

  1. Это то, что происходит в памяти. Программы, как правило, не «сбрасывают» значения, с которыми они работают, поскольку это не потребует дополнительных затрат. Rust позволяет избежать проблем, помечая переменную как перемещенную и не разрешая доступ к перемещенной переменной.

  2. В этом случае i32 реализует Copy, что означает, что к ней можно обращаться значение после того, как оно было перемещено. Компилятор фактически позволит нам продолжить доступ к x. Это не было бы верно, если бы x был типом, который не реализовывал Copy, например String или Box.

См. Также:

Pedanti c схема примечания

  • Эта диаграмма не в масштабе . i32 занимает 4 байта, а указатель / ссылка занимает зависящее от платформы число байтов, но проще предположить, что все имеют одинаковый размер.

  • Стек обычно начинается с высокий адрес и увеличивается вниз, а куча начинается с низкого адреса и растет вверх.

2 голосов
/ 24 января 2020

Хотя общее правило точно такое же, как и в этом ответе В чем различия между `String` и` str` в Rust? , я отвечу и здесь.

Ссылка на Rust - это (почти) именно то, что вы описали: указатель на значение где-то в памяти. (Это не всегда. Например, срезы также содержат длину, а указатели на черты также содержат v-таблицу. Они называются жирными указателями). В начале, Box<T> - это значение, как и любое другое значение в Rust, поэтому разница очевидна - один - это ссылка на место в памяти, а второй - это значение где-то в памяти. Путаница заключается в том, что Box<T> внутренне содержит ссылку на память, но эта ссылка размещается в куче, а не в стеке. Разница между этими двумя заключается в том, что стек является локальным для функции и довольно небольшим (на моем macOS это максимум 8192 КиБ).

Например, вы не можете сделать что-то подобное по нескольким причинам:

fn foo() -> &u32 {
    let a = 5;

    &a
}

Самая важная причина в том, что a не будет там после возвращения foo(). Эта память будет стерта (но не всегда), и, возможно, она скоро будет изменена на другое значение. Это неопределенное поведение в C и C ++ и ошибка в Rust, которая не допускает никакого неопределенного поведения (в коде, который не использует unsafe).

С другой стороны, если вы выполните:

fn foo() -> Box<u32> {
    let a = Box::new(5);

    a
}

Произойдет несколько важных для нас вещей:

  • будет выделена память в стеке. Эта память полностью независима от текущей области действия функции, что означает, что она должна быть освобождена, когда она не понадобится
  • , мы переместим значение , поэтому время жизни не требуется
  • владение a будет передано вызывающей стороне

Для удобства Box<T> будет вести себя как ссылка во многих случаях, так как эти два часто могут использоваться взаимозаменяемо. Например, посмотрите эту программу C, в которой мы предоставляем функциональность, аналогичную второму примеру:

int* foo(void) {
  int* a = malloc(sizeof(int));
  *a = 5;

  return a;
}

Как вы можете видеть, указатель используется для хранения адреса памяти, и он передается дальше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...