Инструментальная цепочка для c++ и ассемблера: GNU
У меня есть следующий код c++:
int main(void)
{
int i = 33, j = 66;
swap(i,j);
cout << i << ", " << j << endl;
return(0);
}
Если я теперь проверю сгенерированный код ассемблера, я получаю в области вызова swap
следующее:
movl $33, -24(%rbp)
movl $66, -20(%rbp)
leaq -20(%rbp), %rdx
leaq -24(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call _ZSt4swapIiEvRT_S1_
movl -20(%rbp), %ebx
movl -24(%rbp), %eax
И сама подкачка:
_ZSt4swapIiEvRT_S1_:
.LFB1232:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -24(%rbp)
movq %rsi, -32(%rbp)
movq -24(%rbp), %rax
movl (%rax), %eax
movl %eax, -4(%rbp)
movq -32(%rbp), %rax
movl (%rax), %edx
movq -24(%rbp), %rax
movl %edx, (%rax)
movq -32(%rbp), %rax
movl -4(%rbp), %edx
movl %edx, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
Так что, поскольку я не знаком с ассемблером, значит ли это, что для ссылок адрес переменных присваивается непосредственно функции? И это будет означать, что они не передаются через стек (что имеет смысл, потому что это должны быть ссылки)?
Что это делает? (в зоне вокруг вызова):
movq %rdx, %rsi
movq %rax, %rdi
Что касается компилятора, ссылки - это просто указатели. Разница между ними полностью связана с тем, как их использует программист.
Ссылка - это не что иное, как указатель "просто" с другой нотацией.
Таким образом, в итоге сгенерированный код сборки будет таким же, независимо от того, используете ли вы указатели или ссылки
void swap1(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
void swap2(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
_Z5swap1RiS_:
.LFB0:
.cfi_startproc
movl (%rdi), %eax
movl (%rsi), %edx
movl %edx, (%rdi)
movl %eax, (%rsi)
ret
.cfi_endproc
_Z5swap2PiS_:
.LFB1:
.cfi_startproc
movl (%rdi), %eax
movl (%rsi), %edx
movl %edx, (%rdi)
movl %eax, (%rsi)
ret
.cfi_endproc
REFGUID
определен как const GUID *
в C и const GUID&
в C ++
Извините за длинный ответ, но я думаю, что сборка - лучший способ понять, как ссылки выполняются компиляторами.
#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
-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
, &refToI
содержимое -0xc(%ebp)
, а -0xc(%ebp)
- это место, где refToi
находится, а его содержимое - не что иное, как адрес i
.
Последнее: почему эта строка прокомментирована?
//cout << "*refToNum = " << *refToI << "\n";
Потому что *refToI
не разрешен, и он даст вам ошибку времени компиляции.