- память - это просто огромный массив, который можно индексировать любым смещением (например,
u64
). - Это смещение называется адрес ,
- и переменная, в которой хранится адрес, называемый указателем .
- Однако обычно выделяется только небольшая часть памяти, поэтому не каждый адрес имеет смысл (или действителен).
- Распределение - это запрос на создание (последовательного) диапазона адресов, значимых для программы (чтобы она могла получить доступ / изменить).
- Каждый объект (и под объектом я подразумеваю любой тип) расположен в выделенной памяти (поскольку невыделенная память не имеет смысла для программы).
- Ссылка на самом деле является указателем который гарантирован (компилятором) как действительный (т.е. полученный из адреса некоторого объекта, известного компилятору). Взгляните также на std do c.
Вот пример этих концепций ( игровая площадка ):
// This is, in real program, implicitly defined,
// but for the sake of example made explicit.
// If you want to play around with the example,
// don't forget to replace `usize::max_value()`
// with a smaller value.
let memory = [uninitialized::<u8>(); usize::max_value()];
// Every value of `usize` type is valid address.
const SOME_ADDR: usize = 1234usize;
// Any address can be safely binded to a pointer,
// which *may* point to both valid and invalid memory.
let ptr: *const u8 = transmute(SOME_ADDR);
// You find an offset in our memory knowing an address
let other_ptr: *const u8 = memory.as_ptr().add(SOME_ADDR);
// Oversimplified allocation, in real-life OS gives a block of memory.
unsafe { *other_ptr = 15; }
// Now it's *meaningful* (i.e. there's no undefined behavior) to make a reference.
let refr: &u8 = unsafe { &*other_ptr };
Я надеюсь, что это проясняет большинство вещей, но давайте рассмотрим вопросы подробно.
Почему s
указывает на s1
, а не только на данные в самой куче?
s
- это ссылка (т.е. действительный указатель), поэтому она указывает на адрес s1
. Он может (и, вероятно, будет) оптимизирован компилятором для того, чтобы быть тем же участком памяти, что и s1
, логически , он по-прежнему остается другим объектом, который указывает на s1
.
Как s
указывает на s1
. Выделена ли память с полем ptr
, которое содержит адрес памяти s1
.
Цепочка «указателей» все еще сохраняется, поэтому вызов s.len()
внутренне преобразован в s.deref().len
, и доступ к некоторому байту строкового массива, преобразованного в s.deref().ptr.add(index).deref()
.
На картинке отображаются 3 блока памяти: &s
, &s1
, s1.ptr
разные (если не оптимизированы) адреса памяти. И все они хранятся в выделенной памяти. Первые два фактически хранятся в предварительно выделенной (т.е. перед вызовом функции main
) памяти, называемой стек , и обычно она не называется выделенной памятью (практика, которую я проигнорировал в этом ответьте хоть). Указатель s1.ptr
, напротив, указывает на память, которая была выделена явно пользовательской программой (т.е. после ввода main
).
В s1
, похоже, я смотрю на переменная с указателем, длиной и емкостью. Фактическим указателем здесь является только поле ptr
?
Да, именно так. Длина и емкость - это обычные целые числа без знака.