Исключение нарушения прав доступа при вызове метода

У меня тут странная проблема. Предположим, что у меня есть класс с некоторыми виртуальными методами. При определенных обстоятельствах экземпляр этого класса должен вызывать один из этих методов. В большинстве случаев на этом этапе проблем не возникает, но иногда оказывается, что виртуальный метод нельзя вызвать, потому что указатель на этот метод равен NULL (как показано в VS), поэтому возникает исключение нарушения доступа к памяти. Как это могло случиться?

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

UPD: Хорошо, я вижу, что моя постановка задачи довольно расплывчата, поэтому схематично код выглядит так

void MyClass::FirstMethod() const { /* Do stuff */ }
void MyClass::SecondMethod() const
{
    // This is where exception occurs, 
    // description of this method during runtime in VS looks like 0x000000
    FirstMethod(); 
}

Никаких конструкторов или деструкторов.


person Tony    schedule 11.02.2010    source источник
comment
Вы уверены, что вызываете эти виртуальные методы с допустимыми указателями или ссылками? Возможно ли, что что-то делает недействительным/удаляет ваш указатель перед вызовом такого метода? Вот где я бы начал искать, по крайней мере, без примера кода...   -  person Zoli    schedule 11.02.2010
comment
@zdawg, при сбое приложения this-указатель объекта действителен, но указатель на проблемный метод, принадлежащий этому объекту, равен нулю.   -  person Tony    schedule 11.02.2010
comment
когда вы находитесь в отладчике - что оценивает этот указатель? НУЛЕВОЙ?   -  person Tim    schedule 11.02.2010
comment
@tim, нет, этот указатель оценивается нормально, в нем есть множество полей, которые тоже оцениваются нормально.   -  person Tony    schedule 11.02.2010
comment
NULL или иное повреждение this* обычно не вызывает исключение до тех пор, пока/если не будет получен доступ к переменной-члену. Он не разыменовывается при вызове функций-членов. Возвращаясь к теме, когда я получаю исключение, и непонятно, что его вызывает, первое, что я делаю, это смотрю на сборку в месте сбоя.   -  person Dan Olson    schedule 12.02.2010
comment
@Dan: изначально функции описывались как виртуальные, поэтому можно было ожидать, что указатель нулевого объекта рухнет еще до того, как попадет в виртуальную таблицу. Сейчас вопрос отредактировал, и непонятно, виртуальные они или нет.   -  person Steve Jessop    schedule 12.02.2010


Ответы (7)


Повреждение кучи является вероятным кандидатом. Уязвимость имеет указатель v-таблицы в объекте, обычно это первое поле в объекте. Переполнение буфера для какого-либо другого объекта, который оказывается рядом с объектом, сотрет указатель v-таблицы. Вызов виртуального метода, часто намного позже, сработает.

Другим классическим случаем является плохой указатель this, обычно NULL или низкое значение. Это происходит, когда ссылка на объект, для которого вы вызываете метод, неверна. Метод будет работать как обычно, но сработает, как только попытается получить доступ к члену класса. Опять же, это может быть вызвано повреждением кучи или использованием удаленного указателя. Удачи в отладке; это никогда не бывает легко.

person Hans Passant    schedule 11.02.2010
comment
Проблема в том, что я не могу отладить это. Странно, но исключение не возникает, когда я запускаю приложение в режиме DEBUG в VS. - person Tony; 11.02.2010
comment
Да, повреждение кучи ведет себя именно так. Гораздо менее вероятно, что указатель v-таблицы будет поврежден, распределитель отладки, как правило, ставит защиту вокруг блока. В VS есть файл ‹crtdbg.h›, который поможет вам найти подобные проблемы. - person Hans Passant; 11.02.2010
comment
спасибо, кажется, ссылка на объект повреждена. Я не могу понять, как именно это происходит, но переписывание некоторого кода, связанного с получением этой ссылки, похоже, исправляет эту ошибку. - person Tony; 12.02.2010
comment
Никогда не бывает легко? Valgrind почти всегда указывал мне на эти проблемы в течение нескольких минут. - person Lightness Races in Orbit; 29.07.2011

Возможно, вы вызываете функцию (прямо или косвенно) из конструктора базового класса, который сам не имеет этой функции.

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

Возможно (но маловероятно) вы каким-то образом испортили виртуальную таблицу.

Является ли указатель на функцию нулевым только для этого объекта или для всех других объектов того же типа? Если первое, то указатель vtable не работает, и вы ищете не в том месте. Если последнее, то повреждена сама vtable.

person Steve Jessop    schedule 11.02.2010
comment
агрред. reinterpret_cast определенно испортит виртуальную таблицу в сценарии множественного наследования. - person YeenFei; 12.02.2010

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

person JaredPar    schedule 11.02.2010
comment
Хм, я только что понял, что метод даже не виртуальный. Он не вызывается в конструкторе или деструкторе, это просто аксессор поля, который вызывается во время процедуры перерисовки экрана. - person Tony; 11.02.2010
comment
@Tony: судя по комментарию, который вы только что сделали, похоже, что вы вызываете метод с указателем объекта, который равен NULL (или иным образом недействителен). Компилятор сможет выполнить вызов функции с помощью указателя объекта NULL, поскольку это не виртуальный вызов, который может быть статически разрешен во время компиляции/компоновки. Однако, когда функция пытается фактически получить доступ к члену класса, она попытается сделать это с помощью недопустимого указателя объекта. - person Michael Burr; 11.02.2010

Возможно ли, что указатель «этот» удаляется во время обработки SecondMethod?

Другая возможность заключается в том, что SecondMethod на самом деле вызывается с недопустимым указателем прямо перед собой, и что он просто работает (неопределенным поведением) до вызова вложенной функции, который затем терпит неудачу. Если вы можете добавить код печати, проверьте, используются ли «этот» и/или другие указатели, например, 0xcdcdcdcd или 0xfdfdfdfd в различных точках во время выполнения этих методов. Эти значения (я полагаю) используются VS при распределении/распределении памяти, поэтому, возможно, это работает при компиляции в режиме отладки.

person Mark B    schedule 11.02.2010

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

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

person KPexEA    schedule 12.02.2010
comment
Я только что заметил комментарий (в ответе nobugz) о том, что режим отладки не дает сбоев. Вы все еще можете отладить это в режиме выпуска, но это будет немного сложнее, но все же выполнимо в зависимости от того, насколько хорошо вы работаете с отладчиком и таблицами символов. - person KPexEA; 12.02.2010

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

Попробуйте включить некоторую отладочную информацию в свойствах C++ проекта, перестройте и перезапустите отладчик. Если это поможет, вы увидите все обычные отслеживаемые вещи, такие как стек, переменные и т. д.

person Community    schedule 12.02.2010

Если ваш указатель this имеет значение NULL, повреждение маловероятно. Если вы не обнуляете память, у вас не должно быть.

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

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

person Shing Yip    schedule 12.02.2010