В чем именно заключается опасность использования магических значений отладки (таких как 0xDEADBEEF) в качестве литералов?

StackOverflow https://stackoverflow.com/questions/1681144

Вопрос

Само собой разумеется, что использование жестко закодированных шестнадцатеричных буквенных указателей - это катастрофа:

int *i = 0xDEADBEEF;
// god knows if that location is available

Однако, в чем именно заключается опасность использования шестнадцатеричных литералов в качестве переменных ценности?

int i = 0xDEADBEEF;
// what can go wrong?

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

Кто-нибудь потрудится объяснить реальные опасности использования шестнадцатеричных литералов?


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

Это было полезно?

Решение

Я полагаю, что озабоченность, поднятая в вопросе форматирования IP-адреса ранее сегодня, была связана не с использованием шестнадцатеричных литералов в целом, а с конкретным использованием 0xDEADBEEF.По крайней мере, так я это прочитал.

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

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

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

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

В использовании шестнадцатеричного литерала не больше опасности, чем в любом другом виде литерала.

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

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

За редкими исключениями, ничто не является "постоянным".

Мы предпочитаем называть их "медленными переменными" - их значение меняется настолько медленно, что мы не возражаем против перекомпиляции, чтобы изменить их.

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

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

if( x == 7 )

Что означает "7" в приведенном выше утверждении?Это то же самое, что

d = y / 7;

Это то же самое значение, что и "7"?

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

Мы можем - в какой-то степени - объяснить, откуда взялось "7", включив в код крошечную подсказку.

assertEquals( 7, someFunction(3,4), "Expected 7, see paragraph 7 of use case 7" );

"Константа" должна быть указана - и названа - ровно один раз.

"Результат" в модульном тестировании - это не то же самое, что константа, и требует небольшой осторожности при объяснении, откуда она взялась.

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

Нет никаких причин, по которым вы не должны назначать 0xdeadbeef к переменной.

Но горе тому программисту, который пытается присвоить десятичную 3735928559, или восьмеричный 33653337357, или хуже всего:бинарный 11011110101011011011111011101111.

Большой Эндиан или Маленький Эндиан?

Одна из опасностей заключается в том, что константы присваиваются массиву или структуре с элементами разного размера;в конечный код-особенности компилятора или машины (включая JVM по сравнению с CLR) будут влиять на порядок расположения байтов.

Конечно, эта проблема справедлива и для непостоянных значений.

Вот, по общему признанию, надуманный пример.В чем ценность буфер[0] после последней строчки?

const int TEST[] = { 0x01BADA55,  0xDEADBEEF };
char buffer[BUFSZ];

memcpy( buffer, (void*)TEST, sizeof(TEST));

Я не вижу никаких проблем с использованием его в качестве значения.В конце концов, это всего лишь число.

Нет никакой опасности в использовании жестко закодированного шестнадцатеричного значения для указателя (как в вашем первом примере) в правильном контексте.В частности, при разработке аппаратного обеспечения очень низкого уровня именно так вы получаете доступ к регистрам, отображенным в памяти.(Хотя лучше всего давать им имена, например, с помощью #define .) Но на уровне приложения вам никогда не понадобится выполнять подобное назначение.

Я использую CAFEBABE Я раньше не видел, чтобы он использовался какими-либо отладчиками.

int *i = 0xDEADBEEF;
// god knows if that location is available

int i = 0xDEADBEEF;
// what can go wrong?

Опасность, которую я вижу, одинакова в обоих случаях:вы создали значение флага, которое не имеет непосредственного контекста.Там нет ничего о i в любом случае это позволит мне узнать в 100, 1000 или 10000 строках, что с ним связано потенциально критическое значение флага.То, что вы внедрили, - это ошибка landmine, которую, если я не буду помнить о ее проверке при каждом возможном использовании, я могу столкнуться с ужасной проблемой отладки.Каждое использование i теперь придется выглядеть так:

if (i != 0xDEADBEEF) { // Curse the original designer to oblivion
    // Actual useful work goes here
}

Повторите вышеописанное для всех 7000 экземпляров, где вам нужно использовать i в вашем коде.

Итак, почему вышеприведенное хуже этого?

if (isIProperlyInitialized()) { // Which could just be a boolean
    // Actual useful work goes here
}

Как минимум, я могу выявить несколько критических проблем:

  1. Правописание:Я ужасная машинистка.Насколько легко вы обнаружите 0xDAEDBEEF в обзоре кода?Или 0xDEADBEFF?С другой стороны, я знаю, что моя компиляция немедленно завершится с ошибкой isIProperlyInitialized() (вставьте обязательный s против. z обсуждайте здесь).
  2. Раскрытие смысла.Вместо того чтобы пытаться скрыть свои флаги в коде, вы намеренно создали метод, который может видеть остальная часть кода.
  3. Возможности для соединения.Вполне возможно, что указатель или ссылка подключены к свободно определяемому кэшу.Проверка инициализации может быть перегружена, чтобы сначала проверить, находится ли значение в кэше, затем попытаться вернуть его обратно в кэш и, если все это не удается, вернуть false.

Короче говоря, написать код, который вам действительно нужен, так же просто, как создать таинственное магическое значение.Разработчик кода будущего (которым, вполне вероятно, будете вы) будет вам благодарен.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top