Visual Studio не может показать значение 'this' в режиме деблокирования (с отладочной информацией)

Оригинальный вопрос:

Почему этот указатель 0 в сборке версий VS С++?

При взломе сборки релиза Visual Studio 2008 SP1 с параметрами /Zi (компилятор: отладочный информационный формат - база данных программ) и /DEBUG (компоновщик: генерировать параметры отладки, да), почему 'this' - указатели всегда 0x00000000

EDIT: перефразированный вопрос:

Мой оригинальный вопрос был неясным, извините за это. При использовании отладчика Visual Studio 2008 для перехода через программу я могу видеть все переменные, за исключением локальных переменных-членов объекта. Вероятно, причиной этого является отладчик от этого указателя, но VS всегда говорит об этом 0x00000000, поэтому он не может выводить текущие переменные-члены объекта (он не знает позицию памяти объекта)

При загрузке megadump (как мини-накопитель Windows, но содержащий все пространство памяти процесса), я могу посмотреть на все мои локальные переменные (определенные в функции) и целые древовидные структуры в куче, даже если у меня есть указатели к.

Например: при разрыве A:: foo() в режиме выпуска

'this' будет иметь значение 0x00000000
'f_' покажет мусор

Как-то эта информация должна быть доступна для процесса. Это недостающая функция в VS2008? Любой другой отладчик, который правильно справляется с этим?

class A
{
  void foo() { /*break here*/ }
  int f_;
};

Ответ 1

Как уже упоминалось выше, компиляция в режиме Release делает определенные оптимизации (особенно исключая использование ebp/rbp в качестве указателя на фрейм), которые нарушают предположения, на которые отладчик полагается для определения ваших локальных переменных. Однако, зная, почему это происходит, не очень полезно для отладки вашей программы!

Здесь вы можете обойти это: в самом начале вызова метода (нарушение первой строки функции, а не открывающей скобки) указатель this всегда будет находиться в определенном регистре ( ecx на 32-битных системах или rcx на 64-битных системах). Отладчик знает это и поэтому вы должны иметь возможность видеть значение this прямо в начале вызова метода. Затем вы можете скопировать адрес из столбца "Значение" и посмотреть, что конкретно (как (MyObject *)0x003f00f0 или что-то еще), что позволит вам увидеть в this позже в методе.

Если это не достаточно хорошо (например, потому что вы хотите остановить только тогда, когда обнаруживается ошибка, которая является очень небольшим процентом времени, когда данный метод вызывается), вы можете попробовать это немного более продвинутое (и меньше надежный) трюк. Обычно указатель this извлекается из ecx/rcx очень рано в вызове функции, потому что это регистр "вызывающий-сохраняет", что означает, что его значение может быть сбито и не восстановлено функциями, вызываемыми вашим методом (это также необходимо для некоторых инструкций, которые могут только использовать этот регистр для их операнда, например REP * и некоторые инструкции по сдвигу). Однако, если ваш метод часто использует указатель this (включая неявное использование ссылок на переменные-члены или вызовы виртуальных функций-членов), компилятор, вероятно, сохранил this в другом регистре, регистр "callee-saves" (что означает, что любая функция, которая сжимает ее, должна восстановить ее перед возвратом).

Практический результат заключается в том, что в окне просмотра вы можете попробовать посмотреть (MyObject *) ebp, (MyObject *) esi и т.д. с другими регистрами, пока не найдете, что вы смотрите на указатель, который, вероятно, правильный (поскольку переменные-члены совпадают с вашим ожиданием содержимого this во время вашей точки останова). На x86 регистры, сохраненные с помощью calle, - ebp, esi, edi и ebx. На x86-64 они равны rbp, rsi, rdi, rbx, r12, r13, r14 и r15. Если вы не хотите искать все это, вы всегда можете попытаться разобрать пролог своей функции, чтобы увидеть, что копируется ecx (или rcx).

Ответ 2

Локальные переменные (включая this) при просмотре в окне "Локали" не могут быть использованы в сборке "Релиз" так, как они могут быть в сборках Debug. Независимо от того, правильно ли указано значение переменной в любой заданной инструкции, зависит от того, как базовый регистр используется в этой точке. Если код работает нормально в Debug, то вряд ли это значение действительно 0.

Оптимизация в сборках релизов делает значения в окне "Локалицы" дерьмом, невооруженным глазом. Без одновременного отображения и корреляции окна "Разборка" вы не можете быть уверены, что окно "Локали" сообщает вам фактическое значение переменной. Если вы выполните код (возможно, в разделе "Разборка не исходный" ) в строке, которая фактически использует this, скорее всего вы увидите там действительное значение.

Ответ 3

Потому что вы написали прослушиваемую программу и вызвали функцию-член по указателю NULL.

Изменить: перечитайте свой вопрос. Скорее всего, это потому, что оптимизатор сделал номер на вашем коде, и отладчик больше не может его прочесть. Если у вас есть проблема, связанная с выпуском сборки, то это намек на то, что ваш код имеет изворотливое #ifdef в нем или вы вызываете UB, который просто работает в режиме Debug. Else, отладка с помощью сборки Debug. Тем не менее, это не очень полезно, если у вас действительно есть проблема в режиме Release, которую вы не можете найти.

Ответ 4

Ваша функция foo равна inline (она объявлена ​​в определении класса, поэтому неявно inline) и не имеет доступа к каким-либо элементам. Поэтому оптимизатор, скорее всего, фактически не передаст указатель this вообще при компиляции кода, поэтому он недоступен для отладчика.

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

Иногда информация об отладке достаточно, чтобы отладчик мог на самом деле объединить указатель this и значения локальных переменных. Часто это не так, и указатель this, показанный в окне часов (и, следовательно, переменные-члены), является бессмысленным.

Ответ 5

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

Работает ли программа? Тогда не имеет значения, что указатель this выглядит, по-видимому, нулевым.

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

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

Ответ 6

Являются ли они "const" функциями?

Функция const - это та, которая объявляется с ключевым словом const, и это указывает на то, что он не изменит ни одного из членов, только прочитайте их (например, функции доступа)

Оптимизирующий компилятор может не переносить указатель 'this' на некоторые функции const, если он даже не читает из нестатических переменных-членов

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

Ответ 7

Это не указатель this, который является NULL, а скорее указателем, который вы используете для вызова функции-члена:

class A
{
public:
    void f() {}
};

int main()
{
    A* a = NULL;
    a->f(); // DO'H!  NULL pointer access ...

    // FIX
    A* a = new A;
    a->f(); // Aha!
}

Ответ 8

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

A* b=NULL;
b->foo();

Функция здесь не статична, но называется статическим способом.

Лучшим местом для поиска реального указателя this является просмотр стека. Для нестатических функций класса указатель this ДОЛЖЕН быть первым (скрытым) аргументом вашей функции.

class A
{
  void foo() { } // this is "void foo(A *this)" really
  int f_;
};

Если ваш this prointer имеет значение null здесь, перед вызовом функции возникает проблема. Если указатель здесь правильно, тогда отладчик некорректно испортился.

Я использую Code:: Blocks with Mingw уже много лет, со встроенным отладчиком (gdb) У меня только проблемы с указателем при включении оптимизаций, в противном случае он всегда знает этот указатель и может в любое время вызывать его.