Коли використовувати вказівники в C # /. NET?


76

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

За яких обставин використання вказівників стає неминучим?

Це лише з міркувань продуктивності?

Також чому С # виставляє цю функціональність через небезпечний контекст і вилучає з неї всі керовані переваги? Чи можна теоретично використовувати покажчики використання, не втрачаючи жодних переваг керованого середовища?


Дякую, Річарде, я просто намагаюся дізнатись більше, ставлячи (ще) запитання: О
Джоан Венге

3
Це питання, мабуть, зацікавило б вас: stackoverflow.com/questions/584134/…
Ганс Олссон,

Відповіді:


91

Коли це потрібно? За яких обставин використання вказівників стає неминучим?

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

Або, якщо ви є людиною, яка пише маршальний шар.

Це лише з міркувань продуктивності?

Здається хибним використовувати вказівники в керованій мові з інших причин, крім ефективності.

У переважній більшості випадків ви можете використовувати методи класу Маршала для взаємодії з некерованим кодом. (Можливо, є кілька випадків, коли важко або неможливо використати механізм розподілу для вирішення проблеми взаємодії, але я не знаю жодної.)

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

Чому C # виставляє цю функціональність через небезпечний контекст і вилучає з неї всі керовані переваги?

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

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

Чи можна теоретично використовувати покажчики, не втрачаючи жодних переваг керованого середовища?

Під "перевагами" я маю на увазі такі переваги, як збір сміття, безпека типу та цілісність посилань. Таким чином, ваше питання по суті "чи можна теоретично вимкнути систему безпеки, але все-таки отримати переваги від системи безпеки, яка вмикається?" Ні, явно це не так. Якщо ви вимкнете цю систему безпеки, тому що вам не подобається, як вона дорога, тоді ви не отримаєте переваг від її роботи!


Дякую Еріку за відповідь. Скажіть, будь ласка, що означає "посилальна цілісність"? Це використання посилань замість покажчиків?
Джоан Венге

9
@Joan: Кожне посилання насправді стосується чогось дійсного або є нульовим . Покажчики не мають цього майна; вказівник може посилатися на пам'ять, яка зовсім не корисна. Але керовані посилання мають таку властивість; якщо у вас є посилання на рядок, ця річ завжди є або нульовою, або дійсним рядком; ви гарантовано не потрапите в ситуацію, коли у вас є ненульове посилання на щось, що не є дійсним рядком.
Ерік Ліпперт

Дякую Еріку, я зараз це розумію.
Джоан Венге

2
@masoudkeshavarz: Ні. З керованими вказівниками неможливо підробити вказівник на довільну пам’ять. З некерованими вказівниками в небезпечному коді, давайте просто скажемо, що їх називають "некерованими" та "небезпечними" з певної причини . Ви можете робити все, що завгодно, за допомогою некерованого вказівника в небезпечному коді, включаючи пошкодження структур даних середовища виконання .NET.
Ерік Ліпперт,

1
святе пекло, цілу годину шукав чіткої відповіді «ні», і це дивно. Дякую!
krivar

17

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

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

Все це було б вирішено за допомогою покажчиків, відстежуваних GC; ось що таке посилання.

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


7
+1 за Якщо вам доводиться запитувати, ви, мабуть, не повинні . Відмінна порада :-)
Дарін Димитров

1
Ваш висновок правильний, але більшість ваших пояснень помилкові. Вказівники та посилання нічим не відрізняються від точки зору збирача сміття. Порушує GC, коли вказівник або посилання зберігаються в нетипізованій області пам'яті, оскільки GC більше не знає, чи це просто числове значення, чи адреса керованого об'єкта.
Бен Войгт

1
@SLaks: Я не сказав, що посилання та вказівники нічим не відрізняються, я сказав, що вони не відрізняються з точки зору збирача сміття . GC не міг хвилювати, чи взяли ви адресу елемента масиву, чи почали з вказівника на інший елемент і зробили арифметику, щоб знайти ту, на яку ви вказуєте зараз.
Бен Войгт

1
@SLaks: Навіть у власних C та C ++ арифметика покажчика дозволена лише в межах одного об’єкта / розподілу (наприклад, масиву). Смітник у будь-якому випадку рухає цілі об’єкти разом, покажчики не ламаються.
Бен Войгт

3
@SLaks: Значно. ДО ВСЬОГО, ваш гіпотетичний GC-відстежуваний вказівник справді існує в інших мовах .NET (хоча і з певними обмеженнями - це може бути лише автоматичною змінною), і він підтримує арифметику:interior_ptr
Бен Войгт,

5

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

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

Що стосується того, навіщо потрібні вказівники: Основна причина - це робота з некерованими бібліотеками DLL, наприклад, написаними на C ++

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


Редагувати

Ви торкнулися основної проблеми керованого та некерованого коду ... як звільняється пам’ять?

Ви можете змішувати код для продуктивності, як ви описуєте, ви просто не можете переходити керовані / некеровані межі за допомогою покажчиків (тобто ви не можете використовувати покажчики поза контекстом "небезпеки").

Що стосується того, як вони чистяться ... Ви повинні керувати власною пам’яттю; Об'єкти, на які вказують ваші вказівники, були створені / розподілені (як правило, всередині DLL C ++) за допомогою (сподіваємось) CoTaskMemAlloc(), і вам доведеться звільнити цю пам'ять таким же чином, викликаючи CoTaskMemFree(), інакше у вас буде витік пам'яті. Зверніть увагу, що тільки пам'ять, виділену з, CoTaskMemAlloc()можна звільнити за допомогою CoTaskMemFree().

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

Приклад звільнення пам'яті, взятий звідси :

string[] array = new string[2];
array[0] = "hello";
array[1] = "world";
IntPtr ptr = test(array);
string result = Marshal.PtrToStringAuto(ptr);
Marshal.FreeCoTaskMem(ptr);
System.Console.WriteLine(result);


Ще кілька матеріалів для читання:

Звільнення пам'яті C #, на яку посилається IntPtr . Друга відповідь вниз пояснює різні методи розподілу / вивільнення

Як звільнити IntPtr в C #? Підсилює необхідність вивільнення таким же чином, як було виділено пам'ять

http://msdn.microsoft.com/en-us/library/aa366533%28VS.85%29.aspx Офіційна документація MSDN про різні способи розподілу та вивільнення пам'яті.

Коротше ... вам потрібно знати, як виділялася пам’ять, щоб звільнити її.


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

Головне, що вам потрібно закріпити керований об’єкт, на який ви посилаєтесь, за допомогою fixedблоку. Це запобігає переміщенню пам'яті, на яку ви посилаєтесь, GC, перебуваючи в unsafeблоці. Тут є ряд тонкощів, наприклад, ви не можете перепризначити покажчик, ініціалізований у фіксованому блоці ... вам слід прочитати небезпечні та фіксовані оператори, якщо ви дійсно налаштовані на управління власним кодом.

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

  1. C # дуже оптимізований і дуже швидкий
  2. Ваш код вказівника все ще генерується як IL, який повинен бути змінений (у цей момент починають діяти подальші оптимізації)
  3. Ви не вимикаєте Сміттєзбірник ... Ви просто тримаєте об'єкти, з якими працюєте, поза сферою компетенції GC. Тож кожні 100 мс або близько того, GC все ще перериває ваш код і виконує свої функції для всіх інших змінних у вашому керованому коді.

HTH,
Джеймс


Дякую, але коли ви використовуєте покажчики, як вони будуть "очищені" після закінчення? Чи можна використовувати їх у ситуаціях перф-критиків, а потім повернутися до керованого коду?
Джоан Венге

Дякую Джеймсу за додаткову інформацію.
Джоан Венге

2
@Joan: Звичайно. Але ви несете відповідальність за те, щоб все було очищено, щоб не було заблукалих покажчиків на рухому пам’ять тощо. Якщо ви хочете отримати переваги від вимкнення системи безпеки, вам доведеться взяти на себе витрати на те, що зазвичай робить система безпеки для вас.
Ерік Ліпперт

Дякую Еріку, це має сенс. Але у випадках оптимізації продуктивності за допомогою покажчиків, все одно дані повернуться в керований світ, коли він / вона зробить, так? Як керовані дані -> некеровані дані -> деякі швидкі операції з цими даними -> створення керованих даних з цих некерованих даних -> очищення некерованої пам'яті -> повернення до керованого світу?
Джоан Венге

1
Як додаткове зауваження, ви можете явно повідомити про збір сміття тиску пам'яті з некерованої пам'яті за допомогою GC.AddMemoryPressureта GC.RemoveMemoryPressure. Вам все одно доведеться звільняти пам’ять самостійно, але таким чином збирач сміття враховуватиме некеровану пам’ять при прийнятті рішень щодо планування.
Брайан

3

Найпоширеніші причини явного використання покажчиків у C #:

  • виконуючи низькорівневу роботу (наприклад, маніпуляції рядками), яка дуже чутлива до продуктивності,
  • взаємодія з некерованими API.

Причиною того, чому синтаксис, пов’язаний із вказівниками, було видалено з C # (на мою думку та думку - Джон Скіт відповів би краще B-), було те, що він виявився зайвим у більшості ситуацій.

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

Крім того, ультрадоброзичливий підхід, виявлений у C / C ++, є загальним джерелом помилок. У більшості ситуацій, коли мікропродуктивність взагалі не має значення, краще запропонувати більш жорсткі правила та обмежити розробника на користь меншої кількості помилок, які було б дуже важко виявити. Таким чином, для типових бізнес-застосувань так звані «керовані» середовища, такі як .NET та Java, краще підходять, ніж мови, які передбачають роботу проти чистого металу.


1
Покажчики не видаляються з C #. Може, ви думаєте про Java?
Бен Войгт

Я не маю на увазі вказівники були видалені, але зайвий синтаксис був видалений, тобто не потрібно писати obj->Property, obj.Propertyзамість цього працює. Пояснить мою відповідь.
Ondrej Tucny


1
Бен має рацію; Вам, безперечно, доведеться використовувати стрілки (та зірочки) при розстановці посилань на вказівники в C #. Не плутайте вказівники з посиланнями ; C # підтримує обидва.
Ерік Ліпперт

@ Ерік Ліпперт Хе, так. Однак, думаючи про посилання як про підмножину покажчика, я вибрав слово "покажчик" як більш загальний варіант, щоб пояснити еволюцію посилання - і "без покажчика" мову (її "безпечну" частину до бути правильним) —з простого старого вказівника.
Ondrej Tucny 02.03.11

2

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

Корисно також у разі передачі даних із .NET у застарілі програми VB6, де ви будете маршувати дані в пам’ять, передати вказівник на додаток VB6 за допомогою виграшу обміну повідомленнями, скористатися VB6 copymemory () для отримання даних із керованого простору пам’яті у некеровану пам’ять програм VB6 простір ..

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.