Неинициализированные блоки памяти в VC ++ - PullRequest
5 голосов
/ 15 сентября 2008

Как всем известно, среда выполнения Visual C ++ помечает неинициализированные или просто освобожденные блоки памяти специальными ненулевыми маркерами. Есть ли способ полностью отключить это поведение, не устанавливая вручную всю неинициализированную память в нули? Это вызывает хаос с моими действительными ненулевыми проверками, так как 0xFEEEFEEE != 0.

Хм, возможно, мне следует объяснить немного лучше. Я создаю и инициализирую переменную (через new), и все это идет отлично. Когда я освобождаю его (с помощью удаления), он устанавливает указатель на 0xFEEEFEEE вместо NULL. Когда я вставляю правильную проверку для NULL, как должны делать все хорошие программы, которые управляют собственной памятью, я сталкиваюсь с проблемами, когда 0xFEEEFEEE проходит проверку NULL без проблем. Есть ли хороший способ, кроме ручной установки всех указателей на NULL при их удалении, определить, когда память уже была освобождена? Я бы предпочел не использовать Boost просто потому, что я не хочу накладных расходов, хотя они могут быть небольшими, поскольку это единственное, для чего я буду использовать Boost.

Ответы [ 15 ]

18 голосов
/ 15 сентября 2008

Когда вы создаете указатель, explicity инициализирует его как NULL. Аналогично после delete. В зависимости от значения неинициализированных данных (за исключением нескольких конкретных случаев) возникает проблема.

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

14 голосов
/ 15 сентября 2008
Поведение

VC ++ не должно вызывать хаос при любой проверке valid , которую вы можете выполнить. Если вы видите 0xfeeefeee, значит, вы не записали в память (или не освободили ее), поэтому вам все равно не следует читать из памяти.

8 голосов
/ 15 сентября 2008

Если вы читаете неинициализированную память, ваши проверки, скорее всего, не являются «действительными». Память освобождена. Возможно, он уже используется для чего-то другого. Вы не можете делать какие-либо предположения о содержании неинициализированной памяти в C / C ++.

Java (и, я полагаю, C #) гарантируют, что выделенная память обнуляется перед использованием, и, конечно, сборка мусора не позволяет вам вообще видеть свободную память. Но это не свойство кучи C, которая напрямую раскрывает память.

6 голосов
/ 15 сентября 2008

delete не несет ответственности за сброс всех указателей на объект на NULL. Также вы не должны изменять заполнение памяти по умолчанию для среды выполнения Windows DEBUG, и вы должны использовать что-то вроде boost::shared_ptr<> для указателей в любом случае.

Тем не менее, если вы действительно хотите выстрелить себе в ногу вы можете.

Вы можете изменить заливку по умолчанию для Windows Время выполнения ОТЛАДКИ , используя хук распределителя, подобный этому. Это будет работать только для выделенного объекта HEAP!

int main(int argc,char** arv)
{
  // Call first to register hook    
  _CrtSetAllocHook(&zero_fill);
  // Do other stuff
  malloc(100);
}


int zero_fill(int nAllocType, 
              void* pvData, 
              size_t nSize,
              int nBlockUse, 
              long lRequest, 
              const unsigned char *szFileName, 
              int nLine )
{
  /// Very Importaint !! 
  /// infinite recursion if this is removed !!
  /// _CRT_BLOCK must not do any thing but return TRUE
  /// even calling printf in the _CRT_BLOCK will cause
  /// infinite recursion
  if ( nBlockUse == _CRT_BLOCK )
    return( TRUE );
  switch(nAllocType)
  {
  case _HOOK_ALLOC:
  case _HOOK_REALLOC:
    // zero initialize the allocated space.
    memset(pvData,0,nSize);
    break;
  case _HOOK_FREE:
    break;
  }
  return TRUE;
}
6 голосов
/ 15 сентября 2008

Если вы строите в режиме Release вместо режима Debug, среда выполнения вообще не заполняет неинициализированную память, но все равно не будет нулей. Однако вы должны , а не зависеть от этого поведения - вы должны либо явно инициализировать память самостоятельно с помощью memset (), ZeroMemory () или SecureZeroMemory (), либо установить где-нибудь флаг, указывающий, что памяти еще нет инициализируется. Чтение неинициализированной памяти приведет к неопределенному поведению.

5 голосов
/ 16 сентября 2008

Вы говорите:

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

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

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

4 голосов
/ 17 сентября 2008

Если он работает в режиме релиза, это из-за удачи.

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

Я думаю, что мы все согласны с тем, что следующий код не должен работать.

char * p = new char[16];     // 16 bytes of random trash
strcpy(p, "StackOverflow");  // 13 characters, a '\0' terminator, and two bytes of trash
delete [] p;                 // return 16 bytes to the heap, but nothing else changes;

if (p != NULL)               // Why would p be NULL?  It was never set to NULL
    ASSERT(p[0] == 'S');     // In debug, this will crash, because p = 0xfeeefeee and 
                             // dereferencing it will cause an error.
                             // Release mode may or may or may not work, depending on
                             // other memory operations

Как уже говорил любой другой автор, указатели должны быть установлены на NULL после вызова delete. Делаете ли вы это сами или используете boost, какую-то другую обертку или даже макрос в этой теме - решать только вам.

4 голосов
/ 17 сентября 2008

@ Джефф Хаббард ( комментарий ):

Это фактически непреднамеренно дает мне решение, которое я хочу: я могу установить для pvData значение NULL в _HOOK_FREE и не столкнуться с проблемами с 0xFEEEFEEE для моего адреса указателя.

Если это работает для вас, то это означает, что вы читаете освобожденную память, когда тестируете указатель NULL (т. Е. Сам указатель находится в памяти, которую вы освободили).

Это ошибка.

«Решение», которое вы используете, просто скрывает, а не исправляет ошибку. Когда эта освобожденная память когда-либо выделяется для чего-то другого, вдруг вы будете использовать неправильное значение в качестве указателя на неправильную вещь.

4 голосов
/ 16 сентября 2008

Это действительно очень хорошая функция в VC ++ (и я верю другим компиляторам), потому что она позволяет вам видеть нераспределенную память для указателя в отладчике. Я подумаю дважды, прежде чем отключить эту функцию. Когда вы удаляете объект в C ++, вы должны установить указатель на NULL на случай, если что-то позже попытается удалить объект снова. Эта функция позволит вам определить места, где вы забыли установить указатель на NULL.

3 голосов
/ 06 декабря 2008

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

Выпуск сборки будет зависать на компьютере клиента. Это всегда так.

Я проверил это в отладчике и мои указатели устанавливаются на 0xFEEEFEEE после того, как я позвоню удалить на их.

Указатели не изменяются после того, как вы вызвали их удаление. Это память, на которую они указывают, которая установлена ​​в 0xfeeefeee, 0xfeeefeee, ..., 0xfeeefeee.

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

...