0xDEADBEEF против NULL - PullRequest
       1

0xDEADBEEF против NULL

27 голосов
/ 06 мая 2011

Во всем коде я видел распределение памяти в отладочных сборках с NULL ...

memset(ptr,NULL,size);

Или с 0xDEADBEEF ...

memset(ptr,0xDEADBEEF,size);
  1. Каковы преимущества использования каждого из них и каков обычно предпочтительный способ достижения этого в C / C ++?
  2. Если указателю присвоено значение 0xDEADBEEF, не может ли он по-прежнему ссылаться на действительные данные?

Ответы [ 10 ]

57 голосов
/ 07 мая 2011
  1. Использование memset(ptr, NULL, size) или memset(ptr, 0xDEADBEEF, size) является четким свидетельством того, что автор не понимает, что они делают.

    Во-первых, memset(ptr, NULL, size) действительно будет нулевым-в блок памяти в C и C ++, если NULL определен как целочисленный ноль.

    Однако использование NULL для представления нулевого значения в этом контексте не является приемлемой практикой.NULL - это макрос, введенный специально для контекстов указателя.Второй параметр memset является целым числом, а не указателем.Правильный способ обнуления блока памяти будет memset(ptr, 0, size).Примечание: 0 не NULL.Я бы сказал, что даже memset(ptr, '\0', size) выглядит лучше, чем memset(ptr, NULL, size).

    Более того, самый последний (на данный момент) стандарт C ++ - C ++ 11 - позволяет определять NULL как nullptr.Значение nullptr не может быть неявно преобразовано в тип int, что означает, что приведенный выше код не гарантированно компилируется в C ++ 11 и более поздних версиях.

    На языке C (и ваш вопрос помечен как Cхорошо) макрос NULL может расширяться до (void *) 0.Даже в C (void *) 0 неявно не преобразуется в тип int, что означает, что в общем случае memset(ptr, NULL, size) является просто недопустимым кодом в C.

    Во-вторых, даже если второй параметр memset имеетвведите int, функция интерпретирует его как значение unsigned char.Это означает, что только один младший байт значения используется для заполнения целевого блока памяти.По этой причине memset(ptr, 0xDEADBEEF, size) будет компилироваться, но не будет заполнять целевую область памяти значениями 0xDEADBEEF, как наивно надеялся автор кода.memset(ptr, 0xDEADBEEF, size) эквивалентно memset(ptr, 0xEF, size) (при условии 8-битных символов).Хотя это, вероятно, достаточно хорошо, чтобы заполнить какую-то область памяти преднамеренным «мусором», такие вещи, как memset(ptr, NULL, size) или memset(ptr, 0xDEADBEEF, size), все еще предают серьезный недостаток профессионализма со стороны автора.Как уже отмечалось, идея состоит в том, чтобы заполнить неиспользуемую память значением «мусора».Ноль, конечно, не очень хорошая идея в этом случае, так как этого недостаточно.При использовании memset вы ограничены однобайтовыми значениями, такими как 0xAB или 0xEF.Если этого достаточно для ваших целей, используйте memset.Если вам нужно более выразительное и уникальное значение мусора, например 0xDEDABEEF или 0xBAADFOOD, вы не сможете использовать memset с ним.Вам нужно написать специальную функцию, которая может заполнить область памяти 4-байтовым шаблоном.

  2. Указателю в C и C ++ нельзя присвоить произвольное целочисленное значение (кроме NullКонстанта указателя, т.е. ноль).Такое назначение может быть достигнуто только путем принудительного ввода целочисленного значения в указатель с явным приведением.Формально говоря, результатом такого приведения является реализация, определенная.Результирующее значение может указывать на действительные данные.

10 голосов
/ 06 мая 2011

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

1) Запись после удаления

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

2) Читать после удаления

Если оставить содержимое освобожденного блока нетронутым или даже записать только нули, это увеличит вероятность того, что кто-то, читающий содержимое мертвого блока памяти, все же найдет значения разумными и совместимыми с инвариантами (например, указатель NULL, как во многих архитектурах NULL). это просто двоичные нули или целое число 0, ASCII NUL char или двойное значение 0.0). Вместо этого, записывая «странные» шаблоны, такие как 0xDEADBEEF, для большей части кода, который будет обращаться в режиме чтения, эти байты, вероятно, найдут странные необоснованные значения (например, целое число -559038737 или двойное значение со значением -1.1885959257070704e + 148), возможно, вызывая какой-то другой утверждение о самосогласованности.

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

9 голосов
/ 06 мая 2011

Я бы определенно рекомендовал 0xDEADBEEF. Он четко определяет неинициализированные переменные и обращается к неинициализированным указателям.

Будучи странным, разыменование указателя 0xdeadbeef определенно приведет к сбою в архитектуре PowerPC при загрузке слова и, скорее всего, к сбою на других архитектурах, поскольку память, вероятно, находится за пределами адресного пространства процесса.

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

6 голосов
/ 06 мая 2011

http://en.wikipedia.org/wiki/Hexspeak

Эти «магические» числа служат средством отладки для выявления неверных указателей, неинициализированной памяти и т. Д. Вам нужно значение, которое вряд ли будет появляться при обычном выполнении, и то, что видно при выполнениидампы памяти или проверка переменных.Инициализация в ноль менее полезна в этом отношении.Я предполагаю, что когда вы видите, что люди инициализируются на ноль, это потому, что им нужно иметь это значение на нуле.Указатель со значением 0xDEADBEEF может указывать на допустимое расположение в памяти, поэтому не рекомендуется использовать его в качестве альтернативы NULL.

4 голосов
/ 06 мая 2011

Одной из причин того, что вы обнулили буфер или установили его в специальное значение, является то, что вы можете легко определить, является ли содержимое буфера допустимым или нет в отладчике .

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

2 голосов
/ 06 мая 2011

DEADBEEF является примером HexSpeek . С ним, как программист, вы намеренно передаете условие ошибки.

1 голос
/ 06 мая 2011

Я бы выбрал NULL, потому что гораздо проще обнулить память, чем пройти позже и установить все указатели на 0xDEADBEEF.Кроме того, ничто не мешает 0xDEADBEEF быть действительным адресом памяти на x86 - по общему признанию, это было бы необычно, но далеко не невозможно.NULL более надежен.

В конечном счете, look- NULL является языковым соглашением.0xDEADBEEF просто выглядит красиво и все.Вы ничего не получаете за это.Библиотеки будут проверять наличие указателей NULL, они не проверяют наличие указателей 0xDEADBEEF.Тогда в C ++ идея нулевого указателя даже не привязана к нулевому значению, а просто обозначена литеральным нулем, а в C ++ 0x есть nullptr и nullptr_t.

1 голос
/ 06 мая 2011

Я бы лично рекомендовал использовать NULL (или 0x0), поскольку он представляет NULL, как и ожидалось, и пригодится при сравнении.Представьте, что по какой-то причине вы используете char * и промежуточный элемент в DEADBEEF (не знаю почему), тогда по крайней мере ваш отладчик очень пригодится, чтобы сообщить вам, что его 0x0.

0 голосов
/ 07 апреля 2016

Обратите внимание, что второй аргумент в memset должен быть байтом, то есть он неявно приводится к char или подобному. 0xDEADBEEF для большинства платформ конвертируется в 0xEF (и что-то еще для какой-то странной платформы).

Также обратите внимание, что второй аргумент должен формально быть int, который NULL не является.

Теперь для преимущества выполнения такого рода инициализации. Во-первых, поведение, скорее всего, будет более детерминированным (даже если в результате мы получим неопределенное поведение, поведение на практике будет последовательным).

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

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

Если память используется для хранения указателей, то, действительно, очистка памяти будет означать, что вы получите указатель NULL и, как правило, разыменование, которое приведет к ошибке сегментации (которая будет рассматриваться как ошибка). Однако, если вы используете его по-другому, например, как арифметический тип, вы получите 0, а для многих приложений это не то нечетное число.

Если вместо этого вы используете 0xDEADBEEF, вы получите довольно большое целое число, также при интерпретации данных как с плавающей запятой это также будет довольно большое число (IIRC). Если интерпретировать его как текст, он будет очень длинным и содержать символы, отличные от ascii, и если вы используете кодировку UTF-8, он, вероятно, будет недействительным. Теперь, если его использовать в качестве указателя на какой-либо платформе, он будет не соответствовать требованиям выравнивания для некоторых типов - также на некоторых платформах эта область памяти может быть в любом случае отображена (обратите внимание, что на x86_64 значение указателя будет 0xDEADBEEFDEADBEEF, что вне диапазон для адреса).

Обратите внимание, что при заполнении 0xEF будут очень похожи свойства, если вы хотите заполнить память 0xDEADBEEF, вам нужно будет использовать пользовательскую функцию, так как memset не справляется с задачей.

0 голосов
/ 11 мая 2011

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

Обнаружение неинициализированных переменных путем инициализации памяти с помощьюЗначения «garabage-y» обнаруживают только некоторые виды ошибок в некоторых типах данных.

А обнаружение неинициализированных переменных в отладочных сборках, но не в сборках выпуска, похоже на выполнение процедур безопасности только при testing самолет и говорит летающей публике, что он удовлетворен "хорошо, он протестировал ОК".

НАМ НУЖНА ПОДДЕРЖКА ОБОРУДОВАНИЯ для обнаружения неинициализированных переменных.Как в чем-то вроде «недопустимого» бита, который сопровождает каждый объект адресации памяти (= байт на большинстве наших машин) и который устанавливается ОС в каждом байте VirtualAlloc () (и т. Д. Или эквивалентами в руках других ОС)в приложениях и автоматически очищается при записи байта, но при первом чтении вызывает исключение.

Память достаточно дешевая для этого, а процессоры достаточно быстры для этого.Этот конец полагается на «забавные» шаблоны и заставляет нас всех честно загружаться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...