Вывод ассемблера C ++ - как реализованы ссылки

0

Инструментальная цепочка для 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
  • 2
    Просто указатели, LEAQ генерирует значение. Ваш последний фрагмент - ненужный регистр для передачи аргументов через rsi и rdi. Смотреть на неоптимизированный код не так уж интересно.
  • 0
    Хорошо ... Какую оптимизацию вы бы предложили, чтобы избавиться от "ненужного столкновения с регистрами" (но при этом сохранить интересную партию - вызов функции с использованием ссылок)? Потому что, если я добавлю флаг "-O1", то подкачка будет оптимизирована, и значения будут напрямую переданы в стандартный вывод (что имеет смысл) ...
Показать ещё 1 комментарий
Теги:
assembly
reference

3 ответа

2
Лучший ответ

Что касается компилятора, ссылки - это просто указатели. Разница между ними полностью связана с тем, как их использует программист.

  • 1
    Не обязательно. Ссылка может быть реализована таким образом, чтобы не было никакого косвенного обращения.
  • 1
    @sftrabbit Интересная претензия. У вас есть ссылки на это?
Показать ещё 2 комментария
5

Ссылка - это не что иное, как указатель "просто" с другой нотацией.

Таким образом, в итоге сгенерированный код сборки будет таким же, независимо от того, используете ли вы указатели или ссылки

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
  • 0
    com фактически использует этот факт для упрощения взаимодействия с Си. Макрос REFGUID определен как const GUID * в C и const GUID& в C ++
0

Извините за длинный ответ, но я думаю, что сборка - лучший способ понять, как ссылки выполняются компиляторами.

#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 не разрешен, и он даст вам ошибку времени компиляции.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню