Управление инициализацией памяти ЭЛТ - PullRequest
0 голосов
/ 05 мая 2010

Иногда встречаются ошибки, которые можно воспроизвести только в сборках релиза и / или только на некоторых машинах. Распространенной (но отнюдь не единственной) причиной являются неинициализированные переменные, которые подвержены случайному поведению. Например, неинициализированная BOOL может быть ИСТИНА большую часть времени на большинстве машин, но случайным образом инициализируется как ЛОЖЬ.

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

Я пропускаю доступную ловушку CRT (API, раздел реестра и т. Д.), Которая позволяет это? У кого-нибудь есть другие идеи, чтобы туда добраться?

[Изменить:] Кажется, разъяснения в порядке.

  1. Обычные магические числа действительно имеют много преимуществ, но они не обеспечивают покрытие для инициализаций bool (всегда истинно) или битовых полей, которые проверяются по отдельным битовым маскам или подобным случаям. Последовательная инициализация нуля (которую я, конечно, могу включать и выключать) добавит слой тестирования, который может выявить плохое поведение инициализации, которое в противном случае может быть редким.
  2. Я, конечно, знаю о CrtSetAllocHook . Установленная таким образом ловушка не получает указатель на выделенный буфер (он называется до того, как такой буфер был выделен), поэтому он не может перезаписать его. Перегрузка global new тоже не принесет пользы, так как перезапишет любую допустимую инициализацию конструктора.

[Редактировать:] @ Майкл, не уверен, что вы имеете в виду, переопределяя новое. Простой код вроде -

void* new(...)
{
   void* res = ::new(...);   // constructors now called!
   if(SomeExternalConditionApplies())
      OverWriteBufferWithMyPetValues(res);
}

не будет работать. Вставка и изменение всего кода :: new может сработать, но выглядит довольно страшно (бог знает, что мне нужно для включения и включения #include).

Ответы [ 3 ]

3 голосов
/ 05 мая 2010

Я не следую - если для неинициализированной памяти задано что-то вроде 0xcdcdcdcd вместо 0, то лучше для удаления ошибок, потому что код, скорее всего, получит арифметику «в диапазоне» или специально обработает 0 С очень недопустимым значением ошибки, скорее всего, «быстро провалится», поэтому их можно исправить вместо скрытых.

Значения, которые использует отладочная сборка MSVC, специально разработаны, чтобы помочь вызвать сбои, которые могут быть легко обнаружены:

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

Кроме того, их легко распознать на дисплеях данных в отладчике. Ноль не так сильно выделяется.

Все это говорит о том, что MSVC предоставляет несколько отладочных хуков / API, которые вы можете использовать для выполнения действий, которые вы хотите:

Некоторая дополнительная информация в ответ на ваш обновленный вопрос:

Возможно, вы сможете использовать стороннюю библиотеку для отладки, например Dmalloc (http://dmalloc.com/)), но я, честно говоря, не знаю, насколько просто интегрировать эти библиотеки в проект MSVC, особенно в "реальном мире".

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

Вы можете использовать глобальное переопределение operator new() для работы с выделениями, которые происходят с использованием new в C ++ - нет проблем с перезаписью любых допустимых инициализаций конструктора - распределение new происходит до того, как конструктор выполняет любую инициализацию (если вы подумаете об этом на мгновение, должно быть понятно, почему это так).

Кроме того, вы можете рассмотреть возможность перехода на Visual Studio 2010 - он будет зависать в отладчике при использовании неинициализированной локальной переменной - не делая ничего особенного, кроме запуска сборки Debug под отладчиком. Конечно, MSCV некоторое время предупреждал о многих из этих ситуаций, но VS2010 обнаружит следующее в отладчике, который не выдает предупреждений (по крайней мере, с моими текущими настройками компилятора):

int main(  )
{
    unsigned int x;
    volatile bool a = false;

    if (a) {
        x = 0;
    }

    printf( "Hello world %u\n", x); // VS2010 will break here because x is uninitialized

    return 0;
}

Даже версия Express VC ++ 2010 поддерживает это.

1 голос
/ 05 мая 2010

Вход в CRT показывает, что магические числа используются в _heap_alloc_dbg и realloc_help, а само значение кодируется как

static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

Знание того, что искать часто помогает . У связанного потока есть хорошее предложение: установить наблюдение за _bCleanLandFill и изменить его из отладчика.

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

1 голос
/ 05 мая 2010

Просто предложение: можете ли вы использовать инструмент статического анализа кода в вашем компиляторе? / analyse выдаст вам предупреждение C6001, что вы используете неинициализированную память. И это несколько систематично, о чем вы и просите.

...