Как справка реализована внутри? - PullRequest
26 голосов
/ 17 октября 2010

Интересно, как это реализовано на разных компиляторах и в конфигурациях отладки / выпуска? Стандарт как-то дает рекомендации по его реализации? Отличается ли он где-нибудь?

Я пытался запустить простую программу, в которой я возвращал неконстантные ссылки и указатели на локальные переменные из функций, но это сработало так же. Так правда ли, что внутренняя ссылка - это просто указатель?

Ответы [ 8 ]

42 голосов
/ 17 октября 2010

Просто чтобы повторить некоторые вещи, о которых все говорили, давайте посмотрим на вывод некоторых компиляторов:

#include <stdio.h>
#include <stdlib.h>

int byref(int & foo)
{
  printf("%d\n", foo);
}
int byptr(int * foo)
{
  printf("%d\n", *foo);
}

int main(int argc, char **argv) {
  int aFoo = 5; 
  byref(aFoo);
  byptr(&aFoo);
}

Мы можем скомпилировать это с LLVM (с отключенной оптимизацией), и мы получим следующее:

define i32 @_Z5byrefRi(i32* %foo) {
entry:
  %foo_addr = alloca i32*                         ; <i32**> [#uses=2]
  %retval = alloca i32                            ; <i32*> [#uses=1]
  %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
  store i32* %foo, i32** %foo_addr
  %0 = load i32** %foo_addr, align 8              ; <i32*> [#uses=1]
  %1 = load i32* %0, align 4                      ; <i32> [#uses=1]
  %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0]
  br label %return

return:                                           ; preds = %entry
  %retval1 = load i32* %retval                    ; <i32> [#uses=1]
  ret i32 %retval1
}

define i32 @_Z5byptrPi(i32* %foo) {
entry:
  %foo_addr = alloca i32*                         ; <i32**> [#uses=2]
  %retval = alloca i32                            ; <i32*> [#uses=1]
  %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
  store i32* %foo, i32** %foo_addr
  %0 = load i32** %foo_addr, align 8              ; <i32*> [#uses=1]
  %1 = load i32* %0, align 4                      ; <i32> [#uses=1]
  %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0]
  br label %return

return:                                           ; preds = %entry
  %retval1 = load i32* %retval                    ; <i32> [#uses=1]
  ret i32 %retval1
}

Тела обеих функций идентичны

14 голосов
/ 12 июля 2016

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

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

14 голосов
/ 17 октября 2010

Естественная реализация ссылки - это действительно указатель.Однако не зависите от этого в вашем коде.

4 голосов
/ 29 декабря 2016

В словах Бьярне:

Как и указатель, ссылка является псевдонимом для объекта, обычно реализуется для хранения машинного адреса объекта,и не налагает накладных расходов на производительность по сравнению с указателями, но отличается от указателя тем, что:

• Вы получаете доступ к ссылке с точно таким же синтаксисом, что и имя объекта.

• Aссылка всегда ссылается на объект, к которому она была инициализирована.

• Нет '' нулевой ссылки '', и мы можем предположить, что ссылка относится к объекту


Хотя ссылка на самом деле указатель , но его не следует использовать как указатель , а как псевдоним .

2 голосов
/ 03 ноября 2016

Нет необходимости указывать ссылку.Во многих случаях это так, но в других случаях это просто псевдоним, и нет необходимости в отдельном выделении памяти для указателя.Примеры сборки не всегда верны, потому что они сильно зависят от оптимизации и от того, насколько «умным» является компилятор.

например: int i;int & j = i;

не нужно генерировать какой-либо дополнительный код или выделять дополнительную память.

1 голос
/ 16 сентября 2013

Ссылка не указатель. Это факт. Указатель может связываться с другим объектом, имеет свои собственные операции, такие как разыменование и увеличение / уменьшение.

Хотя внутри, ссылка может быть реализована как указатель. Но это деталь реализации, которая не меняет того факта, что ссылки не могут быть заменены указателями. И нельзя писать код, предполагая, что ссылки реализованы как указатели.

1 голос
/ 17 октября 2010

Не могу точно сказать, что это правильно, но я немного погуглил и нашел следующее утверждение:

Стандарт языка не требует любой конкретный механизм. каждый реализация свободна сделать это в любом Кстати, пока поведение совместимый.

Источник: Bytes.com

0 голосов
/ 09 мая 2019

В целом:

Ссылка, внутренне реализована как постоянный указатель, на который автоматически разыменовывается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...