Подтвердить что ты не робот

Как выглядит ссылка на С++, с точки зрения памяти?

Дано:

int i = 42;
int j = 43;
int k = 44;

Изучая адреса переменных, мы знаем, что каждый из них занимает 4 байта (на большинстве платформ).

Однако, учитывая:

int i = 42;
int& j = i;
int k = 44;

Мы увидим, что переменная i действительно занимает 4 байта, но j принимает none, а k снова берет 4 байта в стеке.

Что здесь происходит? Похоже, что j просто не существует во время выполнения. А как насчет ссылки, которую я получаю как аргумент функции? Это займет некоторое место в стеке...

И пока мы на нем - почему я не могу определить массив или ссылки?

int&[] arr = new int&[SIZE]; // compiler error! array of references is illegal
4b9b3361

Ответ 1

везде встречается ссылка j, она заменяется на адресом i. Таким образом, основной адрес ссылочного содержимого разрешается во время компиляции, и нет необходимости разыгрывать его как указатель во время выполнения.

Просто чтобы уточнить, что я имею в виду по адресу i:

void function(int& x)
{
    x = 10;
}

int main()
{
    int i = 5;
    int& j = i;

    function(j);
}

В приведенном выше коде j не должно занимать место в главном стеке , но ссылка x функции strong > займет место в своем стеке. Это означает, что при вызове функции с j в качестве аргумента адрес i, который будет помещен в стек функции strong > . Компилятор может и не должен резервировать место в основном стеке для j.

Для части массива стандарты говорят:

С++ Standard 8.3.2/4:

Не должно быть ссылок на ссылки, нет массивов ссылок, и нет указателей на ссылки.

Почему массивы ссылок являются незаконными?

Ответ 2

Как выглядит ссылка на С++, память-накрест?

Это не так. Стандарт С++ только говорит, как он должен себя вести, а не как его реализовать.

В общем случае компиляторы обычно используют ссылки в качестве указателей. Но они обычно имеют больше информации о том, что может указывать ссылка, и использовать ее для оптимизации.

Помните, что единственным требованием для ссылки является то, что она ведет себя как псевдоним для ссылочного объекта. Поэтому, если компилятор встречает этот код:

int i = 42;
int& j = i;
int k = 44;

то, что он видит, не "создает указатель на переменную i" (хотя именно так компилятор может реализовать его в некоторых случаях), а скорее "сделайте примечание в таблице символов, что j теперь является псевдонимом для i."

Компилятору не нужно создавать новую переменную для j, она просто должна помнить, что всякий раз, когда j ссылается с этого момента, она должна поменять ее и использовать i вместо.

Что касается создания массива ссылок, вы не можете этого сделать, потому что это будет бесполезно и бессмысленно.

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

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

Ответ 3

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

Ответ 4

Вы не можете определить массив ссылок, потому что для их инициализации нет синтаксиса. С++ не допускает неинициализированных ссылок. Что касается вашего первого вопроса, компилятор не обязан выделять пространство для ненужных переменных. Нет никакого способа, чтобы J указывала на другую переменную, поэтому она фактически просто псевдоним для я в области функций и то, как компилятор относится к ней.

Ответ 5

Что-то, о чем упоминается только в другом месте - как заставить компилятор посвятить некоторое пространство памяти ссылке:

class HasRef
{
    int &r;

public:
    HasRef(int &n)
        : r(n) { }
};

Это лишает компилятор возможности просто рассматривать его как псевдоним времени компиляции (альтернативное имя для одного и того же хранилища).

Ответ 6

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

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

Ответ 7

Ссылки фактически не существуют физически, пока они не должны иметь физическое проявление (т.е. как член совокупности).

Наличие массива ссылок является незаконным, вероятно, из-за вышеизложенного. Но ничто не мешает вам создавать массив структур/классов, имеющих ссылочные элементы.

Я уверен, что кто-то укажет стандартное предложение, в котором упоминается все это.

Ответ 8

Это не исправлено - компилятор имеет большую свободу в том, как реализовать ссылку в каждом конкретном случае. Поэтому в вашем втором примере он рассматривает j как псевдоним для i, больше ничего не нужно. При передаче параметра ref он также может использовать смещение стека, опять же никаких накладных расходов. Но в других ситуациях он может использовать указатель.