У чому різниця між посиланням на C # та покажчиком?


85

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

Однією з причин, про яку я запитую, є те, що існує думка, що люди повинні добре розуміти вказівники (від C, я думаю), щоб бути хорошим програмістом. Багато людей, які вивчають мови вищого рівня, пропускають це і тому мають таку слабкість.

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

Я пропустив масивний момент?


1
Коротка відповідь - так, ви пропустили щось досить важливе, і це є причиною "... сприйняття того, що люди повинні розуміти вказівки". Підказка: C # - це не єдина мова.
jdigital

Відповіді:


50

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

EDIT: Концептуально, так. Вони більш-менш однакові.


Чи немає іншої команди, яка заважає посиланню C # переміщувати об'єкт, на який посилається, за допомогою GC?
Річард

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

Так, GCHandle.Alloc, або маршал.AllocHGlobal (за межами фіксованого)
ctacke

Виправлено в C #, pin_ptr у C ++ / CLI
Мехрдад Афшарі

Marshal.AllocHGlobal взагалі не буде розподіляти пам’ять в керованому купі, і, природно, він не підлягає збору сміття.
Мехрдад Афшарі

130

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

Візьмемо для прикладу наступний код

int* p1 = GetAPointer();

Це безпечно для типу в тому сенсі, що GetAPointer повинен повертати тип, сумісний з int *. Проте все ще немає гарантії, що * p1 насправді буде вказувати на int. Це може бути символ, подвійний або просто вказівник на випадкову пам’ять.

Однак посилання вказує на конкретний об'єкт. Об'єкти можна переміщати в пам'яті, але посилання не можна скасувати (якщо ви не використовуєте небезпечний код). Посилання набагато безпечніші в цьому відношенні, ніж вказівники.

string str = GetAString();

У цьому випадку str має один із двох станів 1) він не вказує на жоден об'єкт, а отже, є нулем, або 2) вказує на дійсний рядок. Це воно. CLR гарантує, що це так. Це не може і не буде для вказівника.


14
Чудове пояснення.
iTayb

13

Посилання - це "абстрактний" вказівник: ви не можете робити арифметику з посиланням і не можете грати в жодні низькорівневі фокуси з його значенням.


8

Основна відмінність між посиланням та покажчиком полягає в тому, що покажчик - це сукупність бітів, вміст яких має значення лише тоді, коли він активно використовується як покажчик, тоді як посилання інкапсулює не тільки набір бітів, але й деякі метадані, які зберігають основи, поінформовані про її існування. Якщо вказівник існує на якийсь об’єкт у пам'яті, і цей об’єкт видаляється, але вказівник не стирається, подальше існування вказівника не заподіє шкоди, якщо або поки не буде зроблена спроба отримати доступ до пам'яті, на яку він вказує. Якщо не буде зроблена спроба використовувати вказівник, ніщо не потурбує його існування. Навпаки, основи на основі посилань, такі як .NET або JVM, вимагають, щоб система завжди могла ідентифікувати кожне існуюче посилання на об'єкт, і кожне існуюче посилання на об'єкт повинно завжди бути абоnull або ж визначити об’єкт належного типу.

Зверніть увагу, що кожне посилання на об’єкт фактично містить два типи інформації: (1) вміст поля об’єкта, який він ідентифікує, та (2) набір інших посилань на той самий об’єкт. Хоча не існує жодного механізму, за допомогою якого система може швидко ідентифікувати всі посилання, що існують на об'єкт, набір інших посилань, що існують на об'єкт, часто може бути найважливішим, що інкапсулюється посиланням (це особливо актуально, коли речі типу Objectвикористовуються як речі, такі як маркери блокування). Хоча система зберігає кілька бітів даних для кожного об'єкта для використання GetHashCode, об'єкти не мають реальної ідентичності, крім набору посилань, які існують на них. Якщо Xмістить єдине діюче посилання на об'єкт, замінюючиXз посиланням на новий об'єкт із однаковим вмістом поля не матиме жодного ідентифікованого ефекту, крім зміни бітів, що повертаються GetHashCode(), і навіть цей ефект не гарантується.


5

Покажчики вказують на місце в адресному просторі пам'яті. Посилання вказують на структуру даних. Всі структури даних весь час переміщалися (ну, не так часто, але час від часу) за допомогою збирача сміття (для ущільнення простору пам'яті). Крім того, як ви вже сказали, структури даних без посилань через деякий час збиратимуть сміття.

Крім того, покажчики можна використовувати лише в небезпечному контексті.


5

Я думаю, що розробникам важливо зрозуміти концепцію вказівника - тобто зрозуміти опосередкованість. Це не означає, що вони обов’язково повинні використовувати покажчики. Важливо також розуміти, що поняття посилання відрізняється від поняття вказівника , хоча і лише тонко, але що реалізація посилання майже завжди є покажчиком.

Тобто, змінна, що містить посилання, - це просто блок пам’яті розміром із вказівник, що містить покажчик на об’єкт. Однак цю змінну не можна використовувати так само, як можна використовувати змінну покажчика. У C # (і C, і C ++, ...) вказівник може бути проіндексований як масив, але посилання не може. У C # посилання відстежується збирачем сміття, вказівник бути не може. У C ++ вказівник може бути перепризначений, посилання не може. Синтаксично та семантично вказівники та посилання досить різні, але механічно вони однакові.


Щось з масивом звучить цікаво, це те, що в основному ви можете сказати вказівнику, щоб компенсувати місце пам’яті, як масив, поки ви не можете зробити це за допомогою посилання? Не можу подумати, коли це буде корисно, але тим не менше цікаво.
Річард

Якщо p - це int * (вказівник на int), то (p + 1) - це адреса, визначена p + 4 байтами (розмір int). І p [1] - те саме, що * (p + 1) (тобто він "переорієнтовує" адресу на 4 байти за p). На відміну від цього, із посиланням на масив (у C #) оператор [] виконує виклик функції.
P Daddy

5

Спочатку я думаю, що вам потрібно визначити "вказівник" у вашій сематиці. Ви маєте на увазі вказівник, який ви можете створити в небезпечному коді з фіксованим ? Ви маєте на увазі IntPtr, який ви отримуєте, можливо, від місцевого дзвінка або маршала.AllocHGlobal ? Ви маєте на увазі GCHandle ? По суті, все це одне і те ж - представлення адреси пам’яті, де щось зберігається - будь то клас, число, структура, що завгодно. І для протоколу, вони, безумовно, можуть бути в купі.

Покажчик (усі вищезазначені версії) є фіксованим елементом. GC не уявляє, що знаходиться за цією адресою, і тому не має можливості керувати пам’яттю або життям об’єкта. Це означає, що ви втратите всі переваги системи збору сміття. Ви повинні керувати пам’яттю об’єкта вручну, і у вас є можливість витоків.

З іншого боку, посилання - це майже "керований вказівник", про який знає GC. Це все ще адреса об’єкта, але тепер GC знає деталі цілі, тому може переміщати її, робити ущільнення, доопрацьовувати, утилізувати та всі інші приємні речі, які робить кероване середовище.

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

Редагувати: Насправді ось хороший приклад того, коли ви можете використовувати "вказівник" в керованому коді - у цьому випадку це GCHandle, але точно те ж саме можна було зробити з AllocHGlobal або за допомогою фіксованого на байтовому масиві або структурі. Я, як правило, віддаю перевагу GCHandle, оскільки він відчуває для мене більше ".NET".


Невелика примха, яка, мабуть, вам не слід говорити тут "керований вказівник" - навіть із лапками - тому що це щось зовсім інше, ніж посилання на об'єкт, в IL. Хоча в C ++ / CLI існує синтаксис керованих покажчиків, вони, як правило, недоступні з C #. У IL вони отримуються за інструкціями (тобто) ldloca та ldarga.
Гленн Слейден,

5

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


1

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


1

Однією з найбільших переваг посилань перед покажчиками є більша простота та читабельність. Як завжди, коли ти спрощуєш щось, ти робиш його простішим у використанні, але ціною гнучкості та контролю ви отримуєте завдяки низькорівневим матеріалам (як згадували інші люди).

Вказівників часто критикують за те, що вони "потворні".

class* myClass = new class();

Тепер кожного разу, коли ви використовуєте його, ви повинні спершу його розмежувати або

myClass->Method() or (*myClass).Method()

Незважаючи на втрату деякої читабельності та додавання складності, людям все одно потрібно було часто використовувати покажчики як параметри, щоб ви могли модифікувати фактичний об'єкт (замість того, щоб передавати його за значенням), а для підвищення продуктивності не потрібно було копіювати величезні об'єкти.

Для мене саме тому посилання спочатку «народилися», щоб забезпечити ту саму користь, що і вказівники, але без усього цього синтаксису покажчика. Тепер ви можете передавати фактичний об'єкт (а не лише його значення) І у вас є більш читабельний, звичайний спосіб взаємодії з об'єктом.

MyMethod(&type parameter)
{
   parameter.DoThis()
   parameter.DoThat()
}

Посилання на C ++ відрізнялися від посилань на C # / Java тим, що як тільки ви присвоїли йому значення, яким воно було, ви не змогли його повторно призначити (і воно повинно бути призначене при оголошенні). Це було так само, як використання вказівника const (вказівник, який не можна перенаправити на інший об'єкт).

Java і C # - це дуже високі сучасні мови, які очистили багато нерівностей, що накопичилися в C / C ++ за ці роки та покажчики, безумовно, було однією з тих речей, які потрібно було "очистити".

Наскільки ваш коментар про знання покажчиків робить вас сильнішим програмістом, це правда в більшості випадків. Якщо ви знаєте, як щось працює, на відміну від простого використання, не знаючи, я б сказав, що це часто може дати вам перевагу. Скільки ребра завжди буде змінюватися. Зрештою, використання чогось, не знаючи, як це реалізовано, є однією з багатьох красунь ООП та інтерфейсів.

У цьому конкретному прикладі, що б знання про вказівники допомогло вам із посиланнями? Розуміння того, що посилання на C # НЕ є самим об’єктом, а вказує на об’єкт, є дуже важливим поняттям.

# 1: Ви НЕ передаєте значення, ну, для початку, коли ви використовуєте вказівник, ви знаєте, що вказівник містить лише адресу, ось і все. Сама змінна майже порожня, і тому її так приємно передавати як аргументи. На додаток до підвищення продуктивності, ви працюєте з фактичним об'єктом, тому будь-які внесені вами зміни не є тимчасовими

№2: Поліморфізм / інтерфейси Якщо у вас є посилання типу інтерфейсу, яке вказує на об’єкт, ви можете викликати лише методи цього інтерфейсу, навіть якщо об’єкт може мати набагато більше можливостей. Об'єкти можуть також по-різному реалізовувати одні й ті ж методи.

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

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


1
Особисто я думаю, що C # був би кращою мовою, якби більшість місць, які використовують, .використовували ->, але foo.bar(123)був синонімом виклику статичного методу fooClass.bar(ref foo, 123). Це дозволило б такі речі myString.Append("George"); [що змінить змінну myString ] і зробить більш очевидною різницю у значенні між myStruct.field = 3;і myClassObject->field = 3;.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.