Питання Яка різниця між змінною вказівника та еталонною змінною в C ++?


Я знаю, посилання є синтаксичний цукор, тому код легше читати та писати.

Але які відмінності?


Підсумок з відповідей та посилань нижче:

  1. Покажчик може бути повторно призначений будь-якою кількістю разів, тоді як посилання не можна перепризначити після обов'язкового з'єднання.
  2. Покажчики не можуть вказувати ніде (NULL), тоді як посилання завжди відноситься до об'єкта.
  3. Ви не можете взяти адресу посилання, як ви можете з покажчиками.
  4. Там немає "еталонної арифметики" (але ви можете взяти адресу об'єкта, зазначеного посиланням, і робити в ньому арифметику вказівника, як у &obj + 5)

Щоб уточнити неправильне уявлення:

Стандарт C ++ дуже обережний, щоб уникнути диктування способів компілятора   реалізувати посилання, але кожен C ++ компілятор реалізує   посилання як покажчики. Тобто така заява, як:

int &ri = i;

якщо це не повністю оптимізовано, виділяє таку ж кількість пам'яті   як покажчик, і розміщує адресу   від i в це сховище.

Отже, вказівник та посилання мають таку ж кількість пам'яті.

Як правило,

  • Використовуйте посилання в параметрах функції та типи повернення, щоб забезпечити корисні та самодокументальні інтерфейси.
  • Використовуйте вказівники для реалізації алгоритмів та структур даних.

Цікаво читати:


2625
2017-09-11 20:03


походження


Я думаю, що пункт 2 повинен бути "Вказівник дозволено бути NULL, але посилання не є. Тільки неправильний код може створити посилання NULL, і його поведінка невизначена". - Mark Ransom
Покажчики - це просто інший тип об'єкта, і як будь-який об'єкт у C ++, вони можуть бути змінними. Посилання, з іншого боку, ніколи не є об'єктами тільки змінні. - Kerrek SB
Це компілює без попереджень: int &x = *(int*)0; на gcc Посилання дійсно може вказувати на NULL. - Calmarius
Довідка - це змінна псевдонім - Khaled.K
Мені подобається, як саме перше речення є загальною помилкою. Посилання мають свою семантику. - Lightness Races in Orbit


Відповіді:


  1. Покажчик може бути перепризначений:

    int x = 5;
    int y = 6;
    int *p;
    p =  &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    Довідка не може, і вона повинна бути призначена при ініціалізації:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Покажчик має власну адресу та розмір пам'яті в стекі (4 байти на x86), тоді як посилання використовує ту саму адресу пам'яті (з вихідною змінною), але також займає деякий простір у стекі. Оскільки посилання має ту саму адресу, що й сама оригінальна змінна, можна вважати посилання як інше ім'я для тієї ж змінної. Примітка: те, що показує вказівник, може бути на стекі або купі. Що стосується довідки. Моє твердження в цьому твердженні полягає не в тому, що покажчик повинен вказати на стек. Покажчик - це лише змінна, яка містить адресу пам'яті. Ця змінна знаходиться в стекі. Оскільки посилання має власне місце на стекі, і оскільки адреса є такою самою, як і змінна, до якої він посилається. Більше про стек проти купи. Це означає, що існує реальна адреса посилання, яку компілятор не повідомить вам.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Ви можете мати вказівники на вказівники на покажчики, що пропонують додаткові рівні індирекції. Оскільки посилання лише пропонують один рівень не напрямки.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. Покажчик може бути призначений nullptr безпосередньо, тоді як посилання не можуть. Якщо ви спробуєте досить важко, і ви знаєте, як, ви можете зробити адресу посилання nullptr. Точно так само, якщо ви спробуєте досить важко, ви можете мати посилання на покажчик, і тоді ця посилання може містити nullptr.

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. Покажчики можуть ітерації над масивом, який можна використовувати ++перейти до наступного пункту, до якого вказує курсор, та + 4 перейти до 5-го елементу. Це незалежно від того, який розмір об'єкт вказує на покажчик.

  6. Покажчик має бути зв'язаний * щоб отримати доступ до місця пам'яті, на який він вказує, тоді як посилання можна використовувати безпосередньо. Покажчик використовує клас / struct -> щоб отримати доступ до своїх членів, тоді як посилання використовує ..

  7. Покажчик - це змінна, яка містить адресу пам'яті. Незалежно від того, як посилання виконується, посилання має ту ж саму адресу пам'яті, що й елемент, на який посилаються.

  8. Список не може бути заповнений в масив, тоді як покажчики можуть бути (згадані користувачем @litb)

  9. Константи посилання можуть бути пов'язані з тимчасовими. Покажчики не можуть (не без певної спрямованості):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    Це робить const& безпечніше для використання в списках аргументів і так далі.


1376
2018-02-27 21:26



... але перестановка NULL невизначена. Наприклад, ви не можете перевірити, чи посилання є NULL (наприклад, & ref == NULL). - Pat Notz
Номер 2 є ніправда Посилання не просто "інше ім'я для тієї ж змінної". Посилання можуть бути передані функціям, збереженим у класах і т. Д. У спосіб, дуже схожий на покажчики. Вони існують незалежно від змінних, до яких вони вказують. - Derek Park
Брайан, стек не є релевантним. Посилання та покажчики не повинні займати місця на стекі. Вони можуть бути виділені на купу. - Derek Park
Брайан, той факт, що змінна (у цьому випадку покажчик або посилання) вимагає простору ні означає, що це вимагає місця на стек. Покажчики та посилання можуть не тільки точка до купи, вони можуть бути насправді виділено на купі - Derek Park
Ще один важливий варіант: посилання не можна набити в масив - Johannes Schaub - litb


Що таке посилання C ++ (для програмістів C)

А. довідка можна розглядати як a постійний покажчик (не плутати з покажчиком на постійне значення!) з автоматичною індіорією, тобто компілятор буде застосовувати * оператор для вас

Всі посилання повинні бути ініціалізовані з не нульовим значенням, або компіляція не зможе. Неможливо отримати адресу довідника - адреса оператора повертає адресу вказаної вартості, а також не може робити арифметику по посиланням.

C-програмісти можуть не сприймати посилання C ++, оскільки воно більше не буде очевидним, коли відбудеться індивідуальність, або якщо аргумент буде переданий за значенням або покажчиком, не дивлячись на підписи функції.

Програмісти C ++ можуть не любити використання покажчиків, оскільки вони вважаються небезпечними - хоча посилання насправді не є безпечнішими за постійні покажчики, за винятком самих тривіальних випадків, - відсутність зручності автоматичної індіорієнтації та несуть різну смислову коннотацію.

Розглянемо наступне твердження з C ++ FAQ:

Хоча посилання часто виконуються за допомогою адреси в   основна мова асемблера, будь ласка ні подумайте про посилання як на   смішний дивлячий покажчик на об'єкт. Довідник є об'єкт. це є   не покажчик на об'єкт, ні копія об'єкта. Це є в   об'єкт

Але якщо посилання дійсно були об'єктом, яким чином можуть бути зволікаючі посилання? У некерованих мовах неможливо, щоб посилання були "безпечнішими", ніж вказівники - там взагалі просто неможливо надійно визначити псевдоніми за межами меж області застосування!

Чому я вважаю посилання C ++ корисними

Виходячи з фону C, посилання на C ++ можуть виглядати як дещо дурна концепція, але, де це можливо, слід використовувати їх замість покажчиків: автоматична індірекція є зручно, і посилання стають особливо корисними при спілкуванні RAII - але не через будь-які сприймані переваги безпеки, а скоріше тому, що вони роблять імітативний код менш незграбним.

RAII є однією з центральних понять C ++, але вона взаємодіє нетривіально з копіювальною семантикою. Передача об'єктів за допомогою посилання дозволяє уникнути цих питань, оскільки не відбувається копіювання. Якщо посилання на мові не було, вам слід скористатися вказівками, які є більш громіздкими для використання, тим самим порушуючи принцип побудови мови, що рішення найкращої практики має бути простішим, ніж альтернативи.


301
2017-09-11 21:43



@kriss: Ні, ви також можете отримати посилання на зворотне посилання, повернувши автоматичну змінну за посиланням. - Ben Voigt
@kriss: практично неможливо, щоб компілятор виявив у загальному випадку. Розглянемо функцію-члену, яка повертає посилання на змінну класу: це безпечно і не повинно бути заборонено компілятором. Тоді виклик, який має автоматичний екземпляр цього класу, викликає цю функцію-член і повертає посилання. Престо: зависання посилання. І так, це стане причиною неприємностей, @ Крісс: це моя точка зору. Багато людей стверджують, що перевагою посилання на покажчики є те, що посилання завжди є дійсними, але це просто не так. - Ben Voigt
@kriss: Ні, посилання на об'єкт автоматичної тривалості зберігання сильно відрізняється від тимчасового об'єкта. У будь-якому разі, я просто надав приклад для вашого твердження, що ви можете отримати лише неправильну довідку, вимкнувши недійсний покажчик. Крістоф правильний - посилання не є безпечнішими, ніж покажчики, програма, яка використовує посилання, виключно може все-таки порушити безпеку типу. - Ben Voigt
Посилання не є свого роду вказівником. Це нове ім'я існуючого об'єкта. - catphive
@catphive: правда, якщо ви йдете за семантикою мови, невірно, якщо ви дійсно дивитеся на реалізацію; C ++ є набагато більш "магічною" мовою, що C, і якщо ви видалите магію з посилань, то в кінцевому підсумку з'явиться вказівник - Christoph


Якщо ви хочете бути дійсно педантичними, є одна річ, яку ви можете зробити з посиланням, що ви не можете робити з покажчиком: продовжити термін служби тимчасового об'єкта. У C ++, якщо ви зв'яжете посилання const з тимчасовим об'єктом, термін служби цього об'єкта стає тривалістю життя еталона.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

У цьому прикладі s3_copy копіює тимчасовий об'єкт, який є результатом об'єднання. Тоді як s3_reference по суті стає тимчасовим об'єктом. Це дійсно посилання на тимчасовий об'єкт, який тепер має той же час життя, що і посилання.

Якщо ви спробуєте це без цього const він не повинен скомпілювати. Ви не можете прив'язувати посилання, не пов'язані з константами, до тимчасового об'єкта, і ви не можете взяти свою адресу з цього питання.


152
2017-09-11 21:06



але що для цього використовується? - Ahmad Mushtaq
Ну, s3_copy створить тимчасову, а потім скопіювати його в s3_copy, тоді як s3_reference безпосередньо використовує тимчасове. Тоді, щоб бути дійсно педантичним, вам слід подивитися на Оптимізацію зворотного значення, за допомогою якої компілятору дозволяється витримувати конструкцію копії в першому випадку. - Matt Price
@digitalSurgeon: Магія є досить потужною. Об'єкт життя продовжується за фактом const & обов'язковий, і тільки тоді, коли посилання виходить за межі деструктора фактичний називається тип посилання (у порівнянні з еталонним типом, який може бути базою). Оскільки це посилання, ніяких розрізів не буде. - David Rodríguez - dribeas
Оновлення для C ++ 11: остання пропозиція слід читати: "Ви не можете прив'язати посилання non-const lvalue на тимчасовий", тому що ви може пов'язати неконстумент rvalue посилання на тимчасове, і воно має однакову поведінку, що продовжує життя. - Oktalist
@AhmadMushtaq: ключовим використанням цього є похідні класи. Якщо нема спадщини, можна також використовувати семантику вартості, яка буде дешевою або вільною через конструкцію RVO / move. Але якщо у вас є Animal x = fast ? getHare() : getTortoise() потім x стикається з класичною проблемою різання, в той час як Animal& x = ... буде працювати правильно. - Arthur Tacca


Всупереч поширеній думці, можна мати посилання, що є NULL.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Отримано, це важче з посиланням - але якщо ви це впораєтеся, ви будете рвати волосся, намагаючись його знайти. Посилання є ні за своєю суттю безпечний в C ++!

Технічно це є недійсна довідка, не нульова довідка. C ++ не підтримує нульові посилання як концепцію, як ви можете знайти на інших мовах. Є й інші види неправомірних посилань. Будь-який невірне посилання викликає привид невизначена поведінка, так само, як буде використано недійсний покажчик.

Фактична помилка полягає у вимкненні вказівника NULL перед присвоєнням посилання. Але я не знаю про будь-яких компіляторах, які генерують будь-які помилки в цьому стані - помилка поширюється до крапки далі в коді. Ось що робить цю проблему такою підступною. У більшості випадків, якщо ви відміняєте вказівник NULL, ви крах прямо на цьому місці, і це не вимагає багато налагодження, щоб зрозуміти це.

Мій наведений приклад короткий і придурений. Ось ще один реальний приклад.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Я хочу ще раз підкреслити, що єдиним способом отримати нульову посиланням є неправильний код, і після його отримання ви отримаєте невизначену поведінку. Це ніколи має сенс перевірити нульову довідку; наприклад, ви можете спробувати if(&bar==NULL)... але компілятор може оптимізувати випис із суті! Дійсна довідка ніколи не може бути NULL, так що з вигляду компілятора порівняння завжди є помилковим, і воно вільно усуває if Застереження як мертвий код - це суть невизначеної поведінки.

Належним способом уникнути труднощів є уникнути відхилення вказівника NULL для створення посилання. Ось автоматичний спосіб це зробити.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Для більш старого погляду на цю проблему від когось із кращих навичок письма, див Нульові посилання від Джима Хіслопа та Герба Саттера.

Ще одним прикладом небезпек відхилення можна побачити нульовий покажчик Викриває невизначену поведінку під час спроби перенести код на іншу платформу від Raymond Chen.


104
2017-09-11 20:07



Цей код містить невизначену поведінку. Технічно, ви не можете нічого робити за допомогою нульового покажчика, крім встановлення його та порівняння. Коли ваша програма викликає невизначену поведінку, вона може робити що-небудь, у тому числі з'являючись, щоб працювати правильно, поки ви не віддасте демонстрацію великому босу. - KeithB
марка має дійсний аргумент. аргумент про те, що покажчик може бути NULL, і ви повинні перевірити це, також не є реальним: якщо ви кажете, що функція вимагає не-NULL, то абонент повинен це зробити. тому, якщо абонент не називає невизначену поведінку. як і марка, з поганою посиланням - Johannes Schaub - litb
Опис помилковий. Цей код може або не створити посилання, яке є NULL. Її поведінка невизначена. Це може створити цілком справедливе посилання. Можливо, воно взагалі не зможе створити посилання. - David Schwartz
@ Давид Шварц, якщо я говорив про те, як речі повинні були працювати відповідно до стандарту, ви були б правильними. Але це ні про що я говорю - я кажу про фактичну спостережувану поведінку з дуже популярним компілятором, і екстраполюючи на основі моїх знань типових компіляторів і архітектур процесора до того, що буде ймовірно статися Якщо ви вважаєте, що посилання мають бути вищими за вказівники, оскільки вони безпечніші і не вважають, що посилання можуть бути поганими, вам буде вичерпано просту проблему колись, як і я. - Mark Ransom
Відмова нульового покажчика неправильний. Будь-яка програма, яка це робить, навіть для ініціалізації посилання невірна. Якщо ви ініціалізуєте посилання з покажчика, слід завжди перевіряти, чи є вказівник дійсним. Навіть якщо це вдасться, основний об'єкт може бути видалений в будь-який час, залишивши посилання, щоб посилання на неіснуючий об'єкт, вірно? Те, що ви говорите, це хороші речі. Я думаю, що реальне питання тут полягає в тому, що посилання НЕ потрібно перевіряти на "нульовість", коли ви бачите один, і покажчик повинен бути, як мінімум, затверджений. - t0rakka


Окрім синтаксичного цукру, посилання на a const покажчик (ні покажчик на a const) Ви повинні встановити, що це стосується, коли ви оголошуєте еталонну змінну, і ви не можете змінити це пізніше.

Оновлення: тепер, коли я думаю про це ще дещо, є важлива відмінність.

Мета цілі покажчика константи можна замінити шляхом прийняття його адреси та використання константи.

Ціль довідки не може бути замінений жодним чином НБ.

Це повинно дозволити компілятору зробити більше оптимізації на довіднику.


97
2017-09-11 22:10



Я думаю, що це найкраща відповідь на сьогоднішній день. Інші говорять про посилання та вказівки, як вони різні звірі, а потім викладають, як вони відрізняються у поведінці. Це не робить речей ніяких простіших imho. Я завжди розумію, що посилання є а T* const з різним синтаксичним цукром (це відбувається, щоб усунути багато * і & з вашого коду). - Carlo Wood
Уявіть, що намагаюсь зробити cout << 42 << "hello" << endl без посилань - pm100
Коротка та найкраща відповідь - Kad
"Мішень цілі покажчика константи можна замінити, прийнявши його адресу та використовуючи const cast". Це невизначена поведінка. Побачити stackoverflow.com/questions/25209838/... для подробиць. - dgnuff
Спроба змінити або референт посилання або значення const покажчика (або будь-якого const scalar) є незаконним. Що ви можете зробити: видаліть кваліфікацію const, яка була додана шляхом неявного перетворення: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);це нормально - curiousguy


Ви забули найважливішу частину:

Доступ до учасників із покажчиками використовується -> 
доступ до членів з посиланнями використовує .

foo.bar є чітко перевершує foo->bar так само, як і Ві є чітко перевершує Emacs :-)


96
2017-09-19 12:23



@Orion Edwards> доступ до члену з покажчиками використовує ->> access-member з посиланнями використовує. Це не 100% вірності. Ви можете мати посилання на покажчик. У цьому випадку ви отримаєте доступ до учасників де-референції вказівника, використовуючи -> struct Node (Node * next; }; Вузол * спочатку; // p - це посилання на покажчик void foo (вузол * & p) (p-> next = first; } Вузол * bar = новий вузол; foo (бар); - ОП: Ви знайомі з поняттями rvalues ​​і lvalues?
Розумні вказівники мають обидва. (методи на клас інтелектуального покажчика) та -> (методи на базовому типі). - JBRWilkinson
@ user6105 Оріон Едвардс Заява дійсно на 100% вірна. "доступ до учасників відхиленого покажчика" У покажчика немає жодного учасника. Об'єкт, на який посилається покажчик, має членів, а доступ до них точно такий -> забезпечує посилання на покажчики, як і сам покажчик. - Max Truxa
чому так . і -> має щось зробити з vi і emacs :) - artm
@artM - це був жарт, і, мабуть, не має сенсу для тих, хто не володіє англійською мовою. Мої вибачення. Щоб пояснити, чи ви краще, ніж emacs, це цілком суб'єктивне. Деякі люди вважають, що він набагато вищий, а інші вважають цілком протилежним. Аналогічним чином, я думаю, що використовую . краще, ніж використовувати ->, але так само, як vi проти emacs, це цілком суб'єктивне, і ви нічого не зможете довести - Orion Edwards


Насправді, посилання не дуже схожий на покажчик.

Компілятор зберігає "посилання" на змінні, пов'язуючи ім'я з адресою пам'яті; це його робота для перекладу будь-якої назви змінної на адресу пам'яті при складанні.

Коли ви створюєте посилання, ви лише повідомляєте компілятору, що ви призначаєте інше ім'я змінній покажчика; тому посилання не можуть "вказувати на нуль", тому що змінна не може бути і не бути.

Покажчики - змінні; вони містять адресу деякої іншої змінної або можуть бути нульовими. Важливо те, що вказівник має значення, а посилання лише має змінну, на яку вона посилається.

Тепер деякі пояснення реального коду:

int a = 0;
int& b = a;

Тут ви не створюєте іншу змінну, до якої вказує a; ви просто додаєте інше ім'я до вмісту пам'яті, що має значення a. Ця пам'ять зараз має два імена, a і b, і це можна вирішити за допомогою будь-якої назви.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

При виклику функції компілятор, як правило, генерує простір пам'яті для аргументів, які потрібно скопіювати. Підпис функції визначає пробіли, які слід створити, і дає назву, яку слід використовувати для цих пробілів. Призначення параметра як посилання лише повідомляє компілятору використання простору пам'яті для введення змінної, а не виділення нового простору пам'яті під час виклику методу. Можливо, буде дивно, що ваша функція буде безпосередньо маніпулювати змінною, оголошеною в області викликів, але пам'ятайте, що при виконанні скомпільованого коду більше немає можливості; є просто рівна пам'ять, а ваш функціональний код може маніпулювати будь-якими змінами.

Зараз можуть бути деякі випадки, коли ваш компілятор може не знати посилання під час компіляції, наприклад, при використанні зовнішньої змінної. Отже, посилання може бути або не виконуватись як покажчик у базовому коді. Але в прикладах, які я дав тобі, вона, швидше за все, не буде реалізована за допомогою покажчика.


57
2017-09-01 03:44



Довідка - це посилання на l-значення, не обов'язково до змінної. Через це це набагато ближче до вказівника, ніж до справжнього псевдоніма (конструкція часу компіляції). Приклади виразів, на які можна посилатися, є * p або навіть * p ++ - Arkadiy
По правді, я просто вказую на те, що посилання не завжди може натискати нову змінну на стек, як це буде вказувати новий покажчик. - Vincent Robert
@VincentRobert: він буде діяти так само, як покажчик ... якщо вбудована функція, то посилання та покажчик будуть оптимізовані. Якщо є виклик функції, адресу об'єкту потрібно буде передати до функції. - Ben Voigt
int * p = NULL; int & r = * p; посилання, що вказує на NULL; if (r) {} -> boOm;) - sree
Цей фокус на етапі компіляції виглядає чудово, поки ви не пам'ятаєте, що посилання можуть бути передані навколо runtime, після чого статичний псевдонім виходить з вікна. (І тоді, посилання є зазвичай реалізовано як покажчики, але стандарт не вимагає цього методу.) - underscore_d


Посилання дуже схожі на покажчики, але вони спеціально створені, щоб бути корисними для оптимізації компіляторів.

  • Список посилань розроблений таким чином, що компіляторові набагато легше простежити, які посилання змінюються на псевдоніми. Дуже важливими є дві основні функції: відсутня «довідкова арифметика» і не перепризначення посилань. Вони дозволяють компілятору з'ясувати, які посилання є псевдонімами, які змінні під час компіляції.
  • Довідникові дозволяється звертатися до змінних, які не мають адрес пам'яті, наприклад, ті, які компілятор вибирає для розміщення в регістрах. Якщо ви візьмете адресу локальної змінної, для компілятора це дуже важко помістити в реєстр.

Як приклад:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Оптимізуючий компілятор може усвідомити, що ми отримуємо доступ до [0] та a [1] досить купу. Будемо хотіти оптимізувати алгоритм, щоб:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Для здійснення такої оптимізації потрібно довести, що під час дзвінка нічого не може змінити масив [1]. Це досить легко зробити. я ніколи не менше 2, тому масив [i] ніколи не може відноситись до масиву [1]. можемомоделіровать () дано a0 як посилання (aliasing масив [0]). Оскільки немає "референтної" арифметики, компілятор просто повинен довести, що, можливо, Modify ніколи не отримує адресу x, і він довів, що нічого не змінює масив [1].

Він також повинен довести, що немає способів, щоб майбутній дзвінок міг читати / писати [0], тоді як у нас є тимчасова реєстрова копія її в a0. Це часто є тривіальним для доведення, оскільки в багатьох випадках очевидно, що посилання ніколи не зберігаються в постійній структурі, як екземпляр класу.

Тепер робіть те ж саме з покажчиками

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

Поведінка однакова; лише зараз важче довести, що, можливо, модифікувати не змінювати масив [1], оскільки ми вже дали йому покажчик; кішка знаходиться поза сумкою. Тепер йому доводиться робити набагато складніше підтвердження: статичний аналіз можемомодифікувати, щоб довести, що він ніколи не записує до & x + 1. Крім того, він повинен довести, що ніколи не заощаджує покажчик, який може посилатися на масив [0], який просто як складно

Сучасні компілятори стають кращими та кращими при статичному аналізі, але завжди приємно допомогти їм і використовувати посилання.

Звичайно, заборонивши таку розумну оптимізацію, компілятори дійсно перетворюватимуть посилання на покажчики, коли це буде потрібно.


50
2017-09-11 20:12



Правда, тіло має бути видимим. Однак, визначивши це maybeModify не приймає адреси нічого пов'язаного x набагато простіше, ніж доведення, що куча покажчиків арифметики не відбувається. - Cort Ammon
Я вважаю, що оптимізатор вже робить те, що "куча покажчиків арифметики не зустрічається" перевірити на безліч інших причин. - Ben Voigt
"Посилання дуже схожі на покажчики" - семантично, у відповідних контекстах, - але з точки зору згенерованого коду, лише в деяких реалізаціях, а не через будь-яке визначення / вимогу. Я знаю, що ви це вказали, і я практично не згоден з жодним із ваших постів, але у нас вже є надто багато проблем, оскільки люди, які надто багато читають у коротких опису, наприклад, "посилань схожі / зазвичай реалізуються як покажчики" . - underscore_d
Я відчуваю, що хтось помилково позначив як застарілий коментар у відповідності з правилами void maybeModify(int& x) { 1[&x]++; }, про які йдеться в інших коментарях вище - Ben Voigt