Подтвердить что ты не робот

0xDEADBEEF против NULL

На протяжении всего кода я видел выделение памяти в отладочных сборках с помощью NULL...

memset(ptr,NULL,size);

Или с помощью 0xDEADBEEF...

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

Ответ 1

  • Использование memset(ptr, NULL, size) или memset(ptr, 0xDEADBEEF, size) - это четкое указание на то, что автор не понимал, что они делают.

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

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

    Кроме того, самый последний (на данный момент) стандарт С++ - С++ 11 - позволяет определять NULL как nullptr. Значение nullptr неявно конвертируется в тип int, что означает, что указанный код не может быть скомпилирован в С++ 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), все еще выдают главный недостаток профессионализма в авторской части.

    Опять же, как уже заметил другой ответ, идея здесь состоит в том, чтобы заполнить неиспользуемую память значением "мусора". Нуль, конечно, не очень хорошая идея в этом случае, так как это не "garbagy". При использовании memset вы ограничены однобайтовыми значениями, например 0xAB или 0xEF. Если это достаточно для ваших целей, используйте memset. Если вы хотите получить более выразительное и уникальное значение мусора, например 0xDEDABEEF или 0xBAADFOOD, вы не сможете использовать memset с ним. Вам нужно будет написать специальную функцию, которая может заполнить область памяти 4-байтным шаблоном.

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

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

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

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

Ответ 10

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

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

Теперь для преимущества этой инициализации. Сначала, конечно, поведение будет скорее детерминированным (даже если мы этим закончим в поведении undefined, поведение на практике будет последовательным).

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

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

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

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

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