Що робить Visual Studio зі видаленим покажчиком і чому?


130

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

Однак у Visual Studio 2012; це, мабуть, не так!

Приклад:

#include <iostream>

using namespace std;

int main()
{
    int* ptr = new int;
    cout << "ptr = " << ptr << endl;
    delete ptr;
    cout << "ptr = " << ptr << endl;

    system("pause");

    return 0;
}

Коли я компілюю і запускаю цю програму, я отримую такий результат:

ptr = 0050BC10
ptr = 00008123
Press any key to continue....

Очевидно, що адресу, яку вказує вказівник на зміни при видаленні, викликається!

Чому це відбувається? Чи має це щось спільне з Visual Studio?

І якщо delete може змінити адресу, на яку він вказує, будь-коли, чому б не видалити автоматично встановити покажчик NULLзамість якоїсь випадкової адреси?


4
Видалити вказівник, не означає, що він буде встановлений на NULL, ви повинні подбати про це.
Метт

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

6
@ tjwrona1992, так, тому що це зазвичай відбувається. Книга лише перераховує найбільш ймовірний результат, а не важке правило.
СергійА

5
@ tjwrona1992 Книга C ++, яку я читав - а назва книги ...?
PaulMcKenzie

4
@ tjwrona1992: Це може бути дивно, але все використання невірного значення вказівника є невизначеним поведінкою, а не лише перенаправленням. "Перевірка куди вказує на" IS, використовуючи значення недозволеним чином.
Бен Войгт

Відповіді:


175

Я помітив, що адреса, що зберігається, ptrзавжди перезаписується 00008123...

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

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

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

Він не тільки пояснює, що робить Visual Studio з покажчиком після його видалення, але також відповідає, чому вони вибрали НЕ, щоб встановити його NULLавтоматично!


Ця "функція" увімкнена в рамках налаштування "Перевірки SDL". Щоб увімкнути / відключити його, перейдіть до: PROJECT -> Properties -> Properties (Властивості конфігурації) -> C / C ++ -> General -> SDL перевірки

Щоб підтвердити це:

Зміна цього параметра та повторний повтор одного і того ж коду дає такий результат:

ptr = 007CBC10
ptr = 007CBC10

"особливість" міститься в лапках, оскільки у випадку, коли у вас є два вказівники на одне місце, виклик видалення лише оздоровить ОДИН з них. Інший залишиться вказувати на недійсне місце ...


ОНОВЛЕННЯ:

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


12
Це приємна знахідка. Я б хотів, щоб МС краще документувала поведінку налагодження, як це. Наприклад, було б непогано знати, яка версія компілятора почала реалізовувати це та які параметри дозволяють / відключати поведінку.
Майкл Берр

5
"з точки зору операційної системи це знаходиться на тій же сторінці пам'яті, що і нульова адреса" - так? Чи не стандартний розмір сторінки (ігнорування великих сторінок) на x86 все ще становить 4 КБ як для Windows, так і для Linux? Хоча я тьмяно пам’ятаю щось про перші 64 кб адресного простору в блозі Раймонда Чена, тому на практиці я вважаю це таким же результатом,
Voo

12
@Voo Windows залишає першу (і останню) оперативної пам'яті вартістю 64 КБ як мертвий простір для лову. 0x8123 падає в там красиво
тріскачки урод

7
Насправді це не заохочує шкідливих звичок, і не дозволяє вам пропустити встановлення вказівника на NULL - це вся причина, яку вони використовують 0x8123замість 0. Покажчик все ще недійсний, але викликає виняток при спробі знешкодження його (добре), і він не проходить перевірки NULL (також добре, тому що це помилка не робити цього). Де місце для шкідливих звичок? Це дійсно просто те, що допомагає вам налагоджувати.
Луань

3
Ну, він не може встановити обох (усіх), тому це другий найкращий варіант. Якщо вам це не подобається, просто вимкніть чеки SDL - я вважаю їх досить корисними, особливо при налагодженні чужого коду.
Луань

30

Ви бачите побічні ефекти параметра /sdlкомпіляції. Увімкнено за замовчуванням для проектів VS2015, він забезпечує додаткові перевірки безпеки, ніж ті, що надаються / gs. Використовуйте Project> Properties> C / C ++> General> SDL перевіряє налаштування, щоб змінити його.

Цитуючи статті MSDN :

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

Майте на увазі, що встановлення видалених покажчиків на NULL є поганою практикою, коли ви використовуєте MSVC. Він перемагає допомогу, яку ви отримуєте як від налагодження Heap, так і з цього параметра / sdl, ви більше не можете виявляти недійсні безкоштовні / видалення викликів у вашій програмі.


1
Підтверджено. Після відключення цієї функції вказівник більше не перенаправляється. Дякуємо, що надали фактичну настройку, яка її змінює!
tjwrona1992

Ганс, чи все ще вважається поганою практикою встановлювати видалені вказівники на NULL у випадку, коли у вас є два вказівники, що вказують на одне місце? Коли ви deleteодин, Visual Studio залишить другий вказівник, який вказує на його початкове місце, яке зараз недійсне.
tjwrona1992

1
Мені зовсім незрозуміло, яку магію ви очікуєте відбутися, встановивши вказівник на NULL. Цей інший покажчик не такий, що він нічого не вирішує, вам все одно потрібен алокатор налагодження, щоб знайти помилку.
Ганс Пасант

3
VS не прибирає покажчики. Це їх псує. Таким чином, ваша програма вийде з ладу, коли ви все одно будете користуватися ними. Розподільник налагоджень робить те саме, що і з накопичувальною пам'яттю. Велика проблема з NULL, вона недостатньо корумпована. В іншому випадку загальна стратегія, google "0xdeadbeef".
Ганс Пасант

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

19

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

Це однозначно оманлива інформація.

Очевидно, що адресу, яку вказує вказівник на зміни при видаленні, викликається!

Чому це відбувається? Чи має це щось спільне з Visual Studio?

Це явно в мовних специфікаціях. ptrнедійсний після дзвінка на delete. Використання ptrпісля його deleted є причиною для невизначеної поведінки. Не робіть цього. Середовище виконання часу вільне робити все, що хоче, ptrпісля дзвінка delete.

І якщо delete може змінити адресу, на яку він вказує, будь-коли, чому б не видалити автоматично встановити вказівник на NULL замість якоїсь випадкової адреси ???

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


1
Я не вірю, що це відповідає на питання ОП.
СергійА

Не згоден навіть після редагування. Якщо встановити його на NULL, це не приховає проблему - насправді, це буде ЕКСПОЗИЦІЯ в більшій кількості випадків, ніж без цього. Є причина, що звичайні реалізації цього не роблять, а причина в іншому.
СергійА

4
@SergeyA, більшість реалізацій не роблять це заради ефективності. Однак якщо програма вирішить її встановити, краще встановити її на щось, що не є NULL. Це виявить проблеми раніше, ніж якби було встановлено NULL. Встановлено значення NULL, дзвінок deleteдвічі по вказівнику не спричинить проблем. Це, безумовно, не добре.
R Sahu

Ні, не ефективність - принаймні, це не головна проблема.
СергійА

7
@SergeyA Якщо встановити вказівник на значення, яке не є, NULLале також безумовно, поза межами адресного простору процесу, буде викрито більше випадків, ніж дві альтернативи. Якщо залишити його звисаючим, не обов'язково буде викликати сегментацію, якщо його використовувати після звільнення; встановивши його так, щоб NULLвін не викликав сегментації, якщо він deleteзнову d.
Blacklight Shining

10
delete ptr;
cout << "ptr = " << ptr << endl;

Загалом, навіть при читанні (як ви робите вище) зауважте: це відрізняється від перенаправлення значень недійсних покажчиків (вказівник стає недійсним, наприклад, коли ви deleteце) - це визначена реалізацією поведінка. Це було внесено в CWG № 1438 . Дивіться також тут .

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


3
Також актуальною є цитата з [basic.stc.dynamic.deallocation]: "Якщо аргумент, який задається функцією делокації в стандартній бібліотеці, є покажчиком, який не є нульовим значенням вказівника, функція делокації повинна розміщувати сховище, на яке посилається вказівник, і робить недійсними всі покажчики, що посилаються на будь-який частина розміщеного місця зберігання "і правило в [conv.lval](розділ 4.1), яке говорить про зчитування (перетворення lvalue-> rvalue) будь-якого недійсного значення вказівника, є поведінкою, визначеною реалізацією.
Бен Войгт

Навіть UB може бути реалізований певним чином певним постачальником таким чином, щоб він був надійним, принаймні для цього компілятора. Якби Microsoft вирішила застосувати функцію санітарії покажчика до CWG # 1438, це не зробило б цю більш-менш надійну, і, зокрема, просто не вірно, що "що-небудь може статися", якби ця функція була включена незалежно від того, що говорить стандарт.
Кайл Странд

@KyleStrand: Я в основному дав визначення UB ( blog.regehr.org/archives/213 ).
giorgim

1
Для більшості С ++ спільноти на SO "все, що могло статися" сприймається занадто буквально . Я думаю, що це смішно . Я розумію визначення UB, але я також розумію, що компілятори - це просто шматки програмного забезпечення, реалізовані реальними людьми, і якщо ці люди реалізують компілятор так, щоб він поводився певним чином, саме так поводитиметься компілятор , незалежно від того, що говорить стандарт .
Кайл Странд

1

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

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

Власне кажучи, чим більше я думаю про це, тим більше я вважаю, що VS винен, коли робить це, як завжди. Що робити, якщо вказівник const? Чи все-таки це змінять?


Так, навіть постійні вказівники переспрямовуються на цей загадковий 8123!
tjwrona1992

Є ще один камінь до VS :) Просто сьогодні вранці хтось запитав, чому вони повинні використовувати g ++ замість VS. Ось це іде.
СергійА

7
@SergeyA, але з іншого боку, дерефікування видаленого вказівника покаже вам по segfault, що ви намагалися знешкодити видалений покажчик, і він не дорівнюватиме NULL. В іншому випадку вона вийде з ладу лише у випадку звільнення сторінки (що малоймовірно). Збій швидше; вирішити швидше.
храповик урод

1
@ratchetfreak "Не вдається швидко, вирішіть швидше" - дуже цінна мантра, але "Невдача швидко, знищивши ключові криміналістичні дані" не запускає такої цінної мантри. У простих випадках це може бути зручно, але в більш складних випадках (тих, яким ми, як правило, потребуємо найбільшої допомоги), стирання цінної інформації зменшує мої інструменти, доступні для вирішення проблеми.
Корт Аммон

2
@ tjwrona1992: Microsoft, на мою думку, робить все правильно. Санітизація одного вказівника краще, ніж взагалі нічого не робити. І якщо це спричинить проблеми у налагодженні, поставте крапку перед поганим викликом видалення. Шанси в тому, що без чогось подібного ви ніколи не помітили б проблеми. І якщо у вас є краще рішення для пошуку цих помилок, тоді використовуйте їх і чому вам байдуже, що робить Microsoft?
Зан Лінкс

0

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

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

У попередні часи MS встановлювала значення 0xcfffffff.

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