Что такое «известное неверное значение указателя»?

В этом ответе на этот вопрос было отмечено, что:

Если вы собираетесь «очистить» указатель в dtor, лучше использовать другую идиому — установить указатель на известное неверное значение указателя.

а также что деструктор должен быть:

~Foo()
{
    delete bar;
    if (DEBUG) bar = (bar_type*)(long_ptr)(0xDEADBEEF);
}

У меня есть два вопроса по поводу этих частей ответа.

Во-первых, как установить указатель на заведомо неверное значение указателя? Как вы можете установить указатель на адрес, который, как вы можете быть уверены, не будет выделен?

Во-вторых, что: if (DEBUG) bar = (bar_type*)(long_ptr)(0xDEADBEEF); вообще делает? Что такое DEBUG? Я не смог найти макрос с таким названием. И что такое long_ptr? Что оно делает?

DEBUG — это не стандартный макрос, а тот, который вы определили бы для работы с этим кодом.
Barmar 24.06.2024 21:34

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

user4581301 24.06.2024 21:34

@Бармар -- хорошо, спасибо

CS Student 24.06.2024 21:36

@user4581301 user4581301 — тогда я должен использовать один из этих «дырочных» адресов? И я это делаю, как мне это сделать? А какие адреса у этих дырок?

CS Student 24.06.2024 21:36

Код просто предполагает, что случайно выбранный адрес, например 0xDEADBEEF, не будет действительным. Не существует стандартного способа получить недопустимый указатель, кроме NULL.

Barmar 24.06.2024 21:38

Причина предпочтения 0xDEADBEEFNULL заключается в том, что это будет более очевидно при отладке. Указатели NULL часто используются для начальных значений, поэтому вы не сможете отличить необновленный указатель от очищенного.

Barmar 24.06.2024 21:39

В 32-битной системе шансы получить DEADBEAF даже без гарантий астрономически низки. Шансы получить его именно там, где он вам не нужен, будут ошеломляюще низкими.

user4581301 24.06.2024 21:40

@Barmar — Но что, если в 0xDEADBEEF хранятся данные? Как он может узнать, случайный ли это мусор или реальный адрес?

CS Student 24.06.2024 21:40

В таком случае просто отстой быть тобой. Но в системе с 32-битной адресацией шансы составляют один из буквально миллиардов. Подумал об этом в 64-битной системе..

user4581301 24.06.2024 21:41

вам это не нужно. если указатель указывает на DEADBEEF, то вы «знаете», что указатель был установлен на это значение, чтобы указать, что оно было очищено. Если это была ложь, ну ладно, 1 выстрел из пары миллиардов.

NathanOliver 24.06.2024 21:41

ты не можешь знать. Поэтому это предположение. Большинство адресов не используются, поэтому маловероятно, что вы выберете что-то действительное.

Barmar 24.06.2024 21:41

Даже если он находится в действующей памяти, вероятность того, что это начало объекта, очень мала. Поэтому, если вы видите это в указателе, вы можете быть практически уверены, что это значение-заполнитель.

Barmar 24.06.2024 21:43

Большой состав: 0xDEADBEEF относительно безопасное значение для плохих указателей, выбранное программистом. (long_ptr)(0xDEADBEEF) преобразует значение в long_ptr, который предположительно является псевдонимом указателя в 32-битной адресной системе. Наверное, сейчас void*. (bar_type*) сообщает компилятору C++: «Да, я действительно хочу нарушить строгое псевдонимы и присвоить bar указатель неправильного типа. Я знаю, что делаю, поэтому не жалуйтесь».

user4581301 24.06.2024 21:46

Я понимаю, о чем вы говорите, но для меня это действительно странно. Вы полагаетесь на удачу. Я мог бы просто использовать всю свою память. Установка случайного адреса предполагает надежду, что ошибка не обнаружится. Разве нет заранее определенных неиспользуемых адресов, на которые вы можете установить указатель в качестве индикатора освобождения памяти?

CS Student 24.06.2024 21:49

Идея использования специального значения, такого как 0xDEADBEEF, заключается в том, что когда вы видите его в отладчике, весьма вероятно, что это освобожденное хранилище. Цель не в том, чтобы реализовать надежную систему, вы не можете (или, я думаю, не должны) писать код, который сравнивает указатели на 0xDEADBEEF для управления ошибками, это просто флаг, который программист может обнаружить во время отладки.

François Andrieux 24.06.2024 21:51

Вы можете использовать всю память, но не принимайте во внимание комментарий Бармара о том, что адрес должен находиться в начале объекта. Если вы распределяете память побайтно, чтобы сделать это более вероятным, у вас возникают другие, более серьезные проблемы.

user4581301 24.06.2024 21:51

@CSStudent Вся суть в том, чтобы повысить вероятность появления ошибки в узнаваемом виде во время тестирования. Если остальная часть программы не имеет неопределенного поведения, то от записи чего-либо в bar нет абсолютно никакого эффекта. Когда деструктор завершит чтение, его поведение будет неопределенным. Но если вы где-то допустили ошибку и случайно попытались прочитать ее после того, как она была уничтожена, то вы хотите, чтобы ваша программа как можно быстрее и жестче вышла из строя во время тестирования. Случайный адрес дает больше шансов, чем ноль или предыдущее значение, которое было действительным ранее.

user17732522 24.06.2024 21:53

Кроме того, чтобы это имело какой-либо эффект, вам необходимо убедиться, что вы компилируете с отключенной оптимизацией во время тестирования/отладки. В противном случае компилятор просто полностью удалит запись, поскольку она не имеет никакого наблюдаемого эффекта.

user17732522 24.06.2024 21:54

И мне очень не нравится, что это делается в деструкторе каждого класса. Вместо этого позвольте вашему компилятору или другому инструменту бесплатно перезаписывать память, используйте ASAN/valgrind или, если это не поддерживается, перезапишите глобальный operator delete, чтобы он делал это за вас при каждом вызове delete.

user17732522 24.06.2024 21:57

И я понятия не имею, почему в приведенном коде 1. используется if (DEBUG), хотя, скорее всего, это будет что-то вроде #ifdef DEBUG, и 2. почему он использует странную комбинацию приведения, когда более простой и понятный bar = reinterpret_cast<bar_type*>(0xDEADBEEF) будет иметь точно такой же эффект.

user17732522 24.06.2024 22:00

Из-за выравнивания указатель с нечетным адресом (например, 0xDEADBEEF) имеет очень большую вероятность оказаться недействительным указателем на многих архитектурах. (Или, даже если оно действительно, менеджер кучи вряд ли будет его malloc.)

Eljay 24.06.2024 22:02

@ user17732522 Странное приведение, вероятно, связано с тем, что код изначально был взят из C, и они не удосужились переписать его в стиле C++.

Barmar 24.06.2024 22:10

@Barmar Я почти уверен, что одинарное приведение (bar_type*)0xDEADBEEF всегда разрешено и в C.

user17732522 24.06.2024 22:14

Вместо этих дрянных старых трюков просто включите Address Sanitizer, мощный инструмент отладки, доступный во всех основных компиляторах. Он отловит для вас множество различных ошибок доступа к памяти, а не только use-after-free.

prapin 24.06.2024 22:36

@user4581301 user4581301 -- если long_ptr эквивалентен void*, тогда зачем он нужен? Что оно делает? Разве bar = (bar_type*) 0xDEADBEEF не будет работать так же?

CS Student 24.06.2024 22:57

@CSStudent Я спросил плакат ответа. Я не вижу никакой пользы для (long_ptr) (что бы это ни было), но это может быть какая-то устаревшая вещь, возможно, принимая во внимание 16-битные указатели короткого перехода.

Ted Lyngmo 24.06.2024 23:05

@TedLyngmo -- Спасибо, я поищу ответ

CS Student 24.06.2024 23:08

Всем тоже спасибо за ответы и советы!

CS Student 24.06.2024 23:08

В приведенном вами примере, скорее всего, кто-то взломал код C, чтобы заставить его работать на C++. В C вы можете присвоить что угодно void* и что угодно из void*, поэтому код, вероятно, изначально выглядел как if (DEBUG) bar = (long_ptr)(0xDEADBEEF);. C++ гораздо более параноидально относится к приведению, потому что приведение — это чертовски быстрый способ взорвать вашу программу, если вы не будете очень и очень осторожны.

user4581301 24.06.2024 23:09

А если вы вернетесь назад во времени, вы увидите действительно интересное использование указателей, например старый Сегмент и смещение, используемый для получения> 16-битной адресации в 16-битных системах. long_ptr вероятно, было бы немного интереснее, чем void* в те дни. Имейте в виду, вы бы не стали вставлять DEADBEAF в качестве адреса в одной из этих систем.

user4581301 24.06.2024 23:14

Кстати, еще один вопрос: он написал: Now if anything has a dangling reference to the Foo object that's been deleted, any use of bar will not avoid referencing it due to a NULL check - it'll happily try to use the pointer and you'll get a crash that you can fix а к чему приведет сбой? разыменование указателя? Потому что, судя по моим тестам, он не дает сбоя при разыменовании указателя после присвоения ему 0xDEADBEEF.

CS Student 25.06.2024 00:32

Лучшее, что я могу сказать, это «Это прискорбно». Если вы хотите аварийно завершить работу при неудачном разыменовании, используйте nullptr. Визуально распознать неправоту не так-то просто, причин припарковать указатель на nullptr много, но почти наверняка произойдет сбой. К сожалению, мы не можем гарантировать, что так и будет, в конце концов, Undefine Behaviour не определен, но я не работал над процессором, который не резервирует некоторое количество пространства вокруг 0 ​​в качестве нейтральной зоны и не приводит к сбою, если кто-то вступает в него. довольно долго.

user4581301 25.06.2024 01:59

А из-за недостатков использования NULL в связанном вопросе рекомендуется не использовать nullptr. Если вы можете найти волшебство, отличное от NULL, которое, как вы можете гарантировать, приведет к сбою, используйте его. Если вы не можете, вы рискуете. Сегодня у нас есть несколько действительно хороших инструментов, таких как valgrind и ASAN, которые вы можете использовать для обнаружения множества подобных проблем, не портя при этом свой код. Рассмотрите возможность их использования.

user4581301 25.06.2024 02:05

Стоит отметить, что ответ был написан в 2011 году. Вы должны знать, что десятилетие — это большой срок для программного обеспечения, и нынешних рекомендаций, таких как asan, тогда просто не существовало.

Useless 25.06.2024 09:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
34
205
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

0xDEADBEEF — это значение указателя, как и любое другое. Строго говоря, не существует «известного неверного значения указателя».

Однако в качестве очень грубого упрощения мы можем предположить, что значения указателя являются случайными. Шансы увидеть 0xDEADBEEF как 32-битное значение составляют 2^-32, то есть: 2,3283064e-10. Гораздо более вероятно, что вас ударит молния или вы найдете 2 четырехлистных перчатки подряд, чем увидеть именно это значение на любом указателе. Другими словами, для всех практических целей мы можем предположить, что 0xDEADBEEF соответствует нашему заданию, когда мы его видим.

DEBUG не является стандартным макросом. Автор просто хотел проиллюстрировать, что выполнение присваивания можно включить в отладочных сборках и отключить в неотладочных сборках (с помощью специального флага).

Память выделяется страницами, и все адреса на одной странице имеют одинаковую действительность.

Barmar 24.06.2024 22:22

Существует дополнительный фактор: 0xDEADBEEF является нечетным, поэтому единственный объект, на который он может указывать, — это объект с выравниванием 1. Он может указывать только на объект размера 1, что еще больше ограничивает его внешний вид в качестве значения указателя.

François Andrieux 24.06.2024 22:23

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

Gene 25.06.2024 01:58

@Gene - Значит, использование «магического числа» или nullptr не имеет значения? Кстати, а что бы ты посоветовал? Назначить nullptr для возможного сбоя и, следовательно, обнаружения ошибок, или оставить указатель как есть после вызова деструктора?

CS Student 25.06.2024 03:45

Вопрос ко всем, пожалуйста, лучшее решение, чем то, что есть: это было бы здорово.

CS Student 25.06.2024 03:47

@CSStudent Лучшее решение — избегать ручного управления памятью. Если вам абсолютно необходим указатель, используйте unique_ptr, который управляет временем жизни за вас. Если все имеет тип RAII, то никаких ошибок памяти возникнуть не может, если где-то в коде нет UB.

NathanOliver 25.06.2024 04:17

@CS Student - вы не назначаете «ловушку» после уничтожения, указатель должен либо выйти за пределы области видимости (предпочтительно), либо вы назначаете ему действительный или нулевой указатель, если он переживет объект.

Gene 25.06.2024 05:23

Поэтому либо используйте unique_ptr, либо, если я использую необработанный указатель, пусть так и будет, если только он не переживет объект, и тогда я должен обработать указатель соответствующим образом.

CS Student 25.06.2024 16:44
unique_ptr — очень хороший инструмент для работы с необработанными указателями. Люди должны иметь в виду: 1) заданный вопрос касался управления необработанными указателями, и 2) 14 лет назад это был 2010 год, поэтому в проектах было очень распространено использование C++ 98 или C++ 03, а не C++ 11. Очевидно, что вопросы и ответы сегодня не столь актуальны.
Michael Burr 26.06.2024 11:13

Если у вас нет доступа на уровне системы, значения указателя, близкие к NULL, являются плохими. Например, 1, 2, 3 и т. д.

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

Пишите хороший код. Не пишите плохой код, чтобы найти плохой код.

Тогда как бы вы воспроизвели метод «магических чисел»?

CS Student 25.06.2024 00:46

Непонятно, о чем вы спрашиваете, но насколько я понимаю, мой ответ: не используйте магические числа.

Dúthomhas 25.06.2024 00:48

@CSStudent, проблема в использовании необработанных указателей в целом. Вы пытаетесь предотвратить ситуацию типа «использование после освобождения», но в большинстве случаев это не сильно поможет. Вы даже делаете это в деструкторе, так что указатель «bar» все равно исчезнет. «Использовать после освобождения» с гораздо большей вероятностью появится в других объектах или потоках, у которых есть собственная копия этого указателя, которая не является «DEADBEEF»...

Christian Stieber 25.06.2024 03:01

@ChristianStieber - и как бы ты тогда решил такую ​​проблему?

CS Student 25.06.2024 03:52

@CSStudent У меня нет такой проблемы. «Использование после освобождения» редко является проблемой в наши дни, поскольку использование автоматического управления ресурсами через деструкторы и интеллектуальные указатели в основном предотвращает это, а также заставляет нас больше знать о времени жизни указателя: если я никогда нигде не храню «необработанный указатель», и теперь я вдруг это произойдет, за этим обязательно стоит подумать, как это взаимодействует с unique_ptr или smart_ptr, из которого оно получено. И хотя valgrind слишком глючен, чтобы быть полезным прямо сейчас, я также слежу за сбоями. Кроме того, библиотеки времени выполнения могут помочь в уничтожении освобожденной памяти.

Christian Stieber 25.06.2024 12:25
Ответ принят как подходящий

как установить указатель на известное неверное значение указателя? Как вы можете установить указатель на адрес, который, как вы можете быть уверены, не будет выделен?

Во-первых, позвольте мне прояснить, что пример кода, на который вы ссылаетесь, представляет собой грязный хак и предназначен для предоставления некоторых рекомендаций по отладке. Он не предназначен для использования в качестве инструмента управления памятью производственного качества; он даже не предназначен для «вставки» кода — это пример техники отладки.

Установка указателя на жестко запрограммированное значение не обязательно будет «плохим указателем», если вы не знаете что-то о целевой среде. 0xDEADBEEF — это значение, которое может оказаться недопустимым указателем во многих средах просто по счастливой случайности. Это значение было выбрано потому, что я видел, как оно использовалось в качестве маркера «недопустимых данных» в другом коде, и его легко обнаружить при просмотре дампов памяти. Я считаю, что это (или, возможно, уже не было - этот ответ был 14 лет назад!) обычно используется для обозначения недействительных/неиспользуемых областей памяти. Подобно некоторым значениям, которые Microsoft использовала в своих версиях библиотеки отладки некоторых процедур управления памятью (см. https://stackoverflow.com/a/370362)

что: if (DEBUG) bar = (bar_type*)(long_ptr)(0xDEADBEEF); вообще делает? Что такое DEBUG? Я не смог найти макрос с таким названием.

Возможно, я не сказал этого явно в ответе, на который вы ссылаетесь, но пример кода правильнее было бы назвать примером псевдокода. if (DEBUG) используется для обозначения фрагмента кода, который выполняется условно, «если это отладочная сборка».

Например, DEBUG может быть макросом (или переменной), которая определена как ненулевая во время отладочной сборки проблемы. Возможно, в командной строке компилятора (возможно, что-то вроде -DDEBUG=1). Макрос DEBUG — это то, что я обнаружил, обычно используется для кода, который включен только в отладочных сборках.

И что такое long_ptr? Что оно делает?

Чтобы облегчить переход от 32-битных к 64-битным системам, MS добавила типы размером с указатели. LONG_PTR — это целочисленный тип, имеющий размер указателя (32 или 64 бита в зависимости от ситуации). Наверное, мне следовало использовать LONG_PTR вместо long_ptr. Я считаю, что приведение технически ненужно, но я думаю, что оно по-прежнему полезно в качестве обозначения, которое ясно дает понять, что целое число «конвертируется» в указатель - идиома кодирования, которая использует грязно выглядящие приведения для выявления грязного взлома.

Итак, вы использовали long_ptr для большей ясности? Если бы вы сделали это только для того, чтобы было более очевидно, что «целое число преобразуется в указатель», то я думаю, что просто увидеть звездочку было бы достаточно. Кроме того, извините за беспокойство, но в своем ответе почему вы написали: Теперь, если что-то имеет висячую ссылку на удаленный объект Foo, любое использование bar не позволит избежать ссылки на него из-за проверки NULL - это Я с радостью попробую использовать указатель, и вы получите сбой, который можно исправить: ? Потому что, насколько я проверил, он не вылетает

CS Student 27.06.2024 19:11

Честно говоря, я не могу вспомнить нюансы того, о чем думал 14 лет назад. Я не думаю, что это важная часть ответа. Современный C++ предоставляет интеллектуальные типы указателей, такие как unique_pointer и shared_pointer, которые, вероятно, будут лучшими инструментами для решения проблемы, которая обсуждалась тогда.

Michael Burr 29.06.2024 18:26

Другие вопросы по теме

Нужен совет по возврату экземпляра объекта с использованием указателя — bad_alloc в ячейке памяти?
Разрешено ли выделять спин-блокировки в стеке?
Неправильные значения при отображении: проблема с указателем C или malloc?
Как я могу получить указатель на управляемую функцию для предоставления неуправляемому коду в качестве обратного вызова?
Почему я не могу получить доступ к местоположению в массиве указателей char с разыменованием указателя int как индекса
Как небезопасно получить конкретную ссылку из Box<dyn Trait>?
Перебирать массив с помощью while
Я хочу изменить значение первой программы с помощью второй программы
Существует ли альтернативный синтаксис для инициализации константного указателя на константные данные?
Ошибка компиляции при использовании функций шаблона C++, которые принимают в качестве аргументов другие функции, которые принимают ссылки на указатели