Как ссылки хранятся внутри С++?
Мне просто интересно, как ссылки хранятся внутри? Я чувствовал, что понимание в глубине этого уровня позволит мне лучше понять указатель на концепцию и ссылку на решение.
Я подозреваю, что он в основном работает так же, как и указатели, но компилятор заботится о обработке указателей. Просьба сообщить.
Ответы
Ответ 1
Ссылки - это просто псевдонимы внутри, компилятор рассматривает их так же, как указатели.
Но для пользователя с точки зрения использования есть несколько тонких различий.
Некоторые из основных отличий:
- Указатели могут быть
NULL
, а ссылки cannot.There ничего не называется NULL
ссылкой.
- Ссылка A
const
продлевает время жизни временной привязки к ней. Нет эквивалента указателям.
Кроме того, ссылки имеют некоторые общие черты с указателями const
(а не указатель на const):
- Ссылки должны быть инициализированы во время создания.
- Ссылка постоянно привязана к одному месту хранения и не может быть впоследствии восстановлена.
Когда вы знаете, что у вас есть что-то (объект) для ссылки, и вы никогда не захотите ссылаться ни на что другое, используйте указатели ссылки else else.
Ответ 2
Нет никакого требования, чтобы ссылка "хранилась" каким-либо образом вообще. Что касается языка, ссылка является просто псевдонимом какого-либо существующего объекта и что все, что должен предоставить любой компилятор.
Совершенно возможно, что нет никакой необходимости хранить что-либо вообще, если ссылка является лишь короткой рукой для какого-либо другого объекта, который уже находится в области видимости, или если функция с ссылочным аргументом встраивается.
В ситуациях, когда ссылка должна быть показана (например, при вызове функции в другой единицы перевода), вы можете практически реализовать T & x
как T * const
и рассматривать каждое вхождение x
как неявное разыменование этот указатель. Даже на более высоком уровне вы можете думать о T & x = y;
и T * const p = &y;
(и соответственно о x
и *p
) как существенно эквивалентном, поэтому это было бы очевидным способом реализации ссылок.
Но, конечно, нет никаких требований, и любая реализация может делать все, что захочет.
Ответ 3
Извините за использование сборки, чтобы объяснить это, но я думаю, что это лучший способ понять, как ссылки выполняются компиляторами.
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int *ptrToI = &i;
int &refToI = i;
cout << "i = " << i << "\n";
cout << "&i = " << &i << "\n";
cout << "ptrToI = " << ptrToI << "\n";
cout << "*ptrToI = " << *ptrToI << "\n";
cout << "&ptrToI = " << &ptrToI << "\n";
cout << "refToNum = " << refToI << "\n";
//cout << "*refToNum = " << *refToI << "\n";
cout << "&refToNum = " << &refToI << "\n";
return 0;
}
Вывод этого кода похож на этот
i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToNum = 10
&refToNum = 0xbf9e52f8
Давайте посмотрим на разборку (я использовал GDB для этого. 8,9 и 10 здесь - номера строк кода)
8 int i = 10;
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp)
Здесь $0xa
- 10 (десятичный), который мы присваиваем i
. -0x10(%ebp)
здесь означает содержимое ebp register
-16 (десятичное). -0x10(%ebp)
указывает на адрес i
на стеке.
9 int *ptrToI = &i;
0x0804869f <main()+25>: lea -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp)
Назначьте адрес от i
до ptrToI
. ptrToI
снова находится в стеке, расположенном по адресу -0x14(%ebp)
, то есть ebp
- 20 (десятичный).
10 int &refToI = i;
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp)
Теперь вот улов! Сравните разборку строк 9 и 10, и вы будете наблюдать, что -0x14(%ebp)
заменяется на -0xc(%ebp)
в строке 10. -0xc(%ebp)
является адресом refToNum. Он выделяется на стеке. Но вы никогда не сможете получить этот адрес от своего кода, потому что вам не обязательно знать адрес.
Итак, ссылка занимает память. В этом случае это стек памяти, так как мы выделили его как локальную переменную. Сколько памяти он занимает? Как много указатель занимает.
Теперь посмотрим, как мы обращаемся к ссылке и указателям. Для простоты я показал только часть фрагмента сборки
16 cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>: mov -0x14(%ebp),%eax
0x08048749 <main()+195>: mov (%eax),%ebx
19 cout << "refToNum = " << refToI << "\n";
0x080487b0 <main()+298>: mov -0xc(%ebp),%eax
0x080487b3 <main()+301>: mov (%eax),%ebx
Теперь сравните две вышеперечисленные строки, вы увидите поразительное сходство. -0xc(%ebp)
- это фактический адрес refToI
, который никогда не доступен для вас. Проще говоря, если вы считаете ссылку ссылкой как обычный указатель, то доступ к ссылке похож на выбор значения по адресу, на который указывает эта ссылка. Это означает, что ниже двух строк кода даст вам тот же результат
cout << "Value if i = " << *ptrToI << "\n";
cout << " Value if i = " << refToI << "\n";
Теперь сравните этот
15 cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>: mov -0x14(%ebp),%ebx
21 cout << "&refToNum = " << &refToI << "\n";
0x080487fb <main()+373>: mov -0xc(%ebp),%eax
Я думаю, вы можете определить, что здесь происходит. Если вы запрашиваете &refToI
, возвращается содержимое адреса адреса -0xc(%ebp)
и -0xc(%ebp)
находится где refToI
, а его содержимое - это только адрес i
.
Последнее: почему эта строка прокомментирована?
//cout << "*refToNum = " << *refToI << "\n";
Потому что *refToI
не разрешен, и он даст вам ошибку времени компиляции.