Извините за использование ассемблера для объяснения этого, но я думаю, что это лучший способ понять, как ссылки реализуются компиляторами.
#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
не разрешен, и это даст вам ошибку времени компиляции.