发布模式下Visual Studio无法显示带有调试信息的'this'值

13

原问题:

为什么在VS c++ release版本中,this指针的值为0?

当使用Visual Studio 2008 SP1的release版本,并选择/Zi(编译器:调试信息格式-程序数据库)和/DEBUG(连接器:生成调试信息,是),进行调试时,为什么“this”指针总是0x00000000?

编辑后的问题:

我最初的问题表述相当不清晰,很抱歉。使用Visual Studio 2008调试程序时,我可以看到所有变量,除了局部对象的成员变量。这可能是因为调试器从this指针中派生这些变量,但是VS始终显示它为0x00000000,因此无法派生当前对象的成员变量(它不知道对象的内存位置)。

当加载一个大型转储文件(类似于Windows minidump,但包含进程的整个内存空间)时,我可以查看所有局部变量(在函数中定义)和整个堆上的树状结构,即使我有指向它们的指针也可以。

例如:在Release模式下打断点,进入A::foo()

'this'将具有值0x00000000
'f_'会显示垃圾值

某种方式需要将这些信息可用于进程中。这是VS2008中缺少的功能吗?是否有其他调试器可以正确处理此问题?

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

你如何验证this指针是否为NULL?在发布版本中,调试器并不总是显示正确的this指针值。 - harper
有一个解决方法。当在调用堆栈中返回1个或多个步骤时,我可以在那里找到对象的指针,具体取决于代码,在该对象上调用函数。 - Pieter
这也可能取决于foo()函数的操作。如果优化器决定不再需要“this”,它可以重用一个寄存器。 - Bo Persson
8个回答

34

正如其他人所提到的,以Release模式编译会进行某些优化(特别是消除ebp/rbp作为帧指针),这破坏了调试器用来确定您本地变量的假设。然而,了解为什么会发生这种情况并不会对调试程序很有帮助!

以下是一种可以解决此问题的方法:在方法调用的最开始(断点放在函数的第一行,而不是左花括号),this指针总是在特定的寄存器中(32位系统上是ecx,64位系统上是rcx)。调试器知道这一点,所以您应该能够在方法调用的开头看到this的值。然后,您可以从"Value"列中复制地址,并将其特别观察(例如(MyObject *)0x003f00f0) ,这将使您能够在方法的后面看到this

如果这还不够好(例如,因为您只想在错误显现时停止,而这在调用给定方法的时间中只占很小的比例),则可以尝试这种更高级(但不太可靠)的技巧。通常,在函数调用的早期,this指针很快就会被取出ecx/rcx,因为它是一个"调用者保存"寄存器,意味着其值可能被破坏,并且不会由方法调用恢复(还需要一些指令才能使用该寄存器作为其操作数,例如REP*和一些移位指令)。然而,如果您的方法经常使用this指针(包括隐式使用成员变量或调用虚成员函数),则编译器可能已将this保存在另一个寄存器中,即"被调用者保存"寄存器(这意味着任何破坏它的函数必须在返回之前恢复它)。

实际上,这意味着在您的监视窗口中,您可以尝试查看(MyObject *) ebp(MyObject *) esi等其他寄存器,直到您发现您正在查看的指针可能是正确的(因为成员变量与您在断点处期望的this的内容相对应)。 在x86上,被调用保存的寄存器是ebp、esi、edi和ebx。 在x86-64上,它们是rbp、rsi、rdi、rbx、r12、r13、r14和r15。如果您不想搜索所有这些,您可以尝试查看函数序言的反汇编,以查看将ecx(或rcx)复制到哪里。


3
您可以通过选项 /Od 来禁用编译器优化,然后一切都会正常。 - xjdrew
很好的答案,谢谢!我尝试了不同注册表值的转换,但没有找到可读取的信息。 - Pieter

7
本地变量(包括this)在Locals窗口中查看时,在Release版本中不能像在Debug版本中那样可靠。变量值是否正确取决于底层寄存器在该点上的使用方式。在Debug中代码运行正常时,变量值为0的可能性极小。
Release版本中的优化使得Locals窗口中的值对肉眼来说难以预测。如果没有同时显示和相关的Disassembly窗口,您无法确定Locals窗口是否告诉您变量的实际值。如果您通过代码(可能是Disassembly而不是Source)步进到实际使用this的行,则更有可能在那里看到有效值。

6
因为您编写了有缺陷的程序并在空指针上调用了成员函数。
编辑:重新阅读您的问题。很可能是因为优化器对您的代码进行了优化,导致调试器无法读取它。如果您遇到特定于 Release 构建的问题,则这表明您的代码中存在可疑的 #ifdef,或者您调用了仅在 Debug 模式下有效的 UB。否则,请使用 Debug 构建进行调试。然而,如果您在 Release 模式下遇到无法找到的问题,那么这并不是非常有帮助的。

1
鉴于特征(所有受影响的函数),这种情况相当不可能。 - Steve Townsend

3
您的函数fooinline(它在类定义中声明,因此隐式为inline),并且不访问任何成员。因此,优化器在编译代码时很可能根本不会传递this指针,因此调试器无法使用。
在发布版本中,优化器将大幅重新排列代码以提高性能,特别是对于inline函数(虽然它也会优化其他函数,尤其是如果启用了整个程序优化)。它可能不会传递this,而是直接传递指向已使用成员的指针,甚至只是在之前的函数调用中加载的寄存器中传递成员的值。
有时调试信息足以使调试器实际上可以拼凑出this指针和局部变量的值。通常情况下,调试器无法使用this指针窗口中显示的指针(因此成员变量)是无意义的。

1

它们是“const”函数吗?

const函数是使用关键字const声明的函数,这表示它不会更改任何成员,只读取它们(就像访问器函数)

优化编译器可能不会打扰将'this'指针传递给某些const函数,如果它甚至不从非静态成员变量中读取

优化编译器可能会搜索可以是const的函数,使它们保持不变,然后不将this指针传递到它们中,导致调试器无法找到钩子。


1

因为这是一个发布版本。优化的整个重点是更改程序的实现细节,同时保留总体功能。

程序还能工作吗? 那么 this 指针看似为空也没关系。

一般来说,在使用发布版本时,您应该预期调试器会感到困惑。代码将被重新排序,变量完全删除或包含奇怪的意外值。

启用优化时,并不保证这些事情的任何结果。但编译器不会破坏您的程序。如果没有优化就能正常工作,那么有了优化后它仍然可以工作。如果突然间不能工作,那就是您的程序存在缺陷,只是在编译器优化和修改代码之后才暴露出来。


0

这里不是 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!
}

1
不一定-因为原始的a未初始化为NULL,它可能包含任何旧垃圾,并且C++并没有定义会发生异常-实际上,在Windows上只会得到一个结构化异常,其他基于Unix的平台将会得到一个信号。 - Puppy
如果是A类,即使是空指针也不会崩溃。 检查它:((class *)(NULL) )->f(); 当您在函数f中访问任何成员变量时,它将崩溃。 无论如何,未初始化的本地变量总是具有垃圾值,并且行为是不可预测的。 - CrazyC

0

正如其他人已经说过的,您应该确保编译器不会执行任何可能混淆调试器的操作,优化很可能会这样做。 如果您像这样静态调用函数,则可能出现指针为NULL的情况:

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

这个函数在这里不是静态的,但以静态的方式调用。

查找真正的this指针的最佳位置是查看堆栈。对于非静态类函数,this指针必须是您函数的第一个(隐藏的)参数。

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

如果你的this指针在这里是空的,那么在调用函数之前就出现了问题。如果指针在这里是正确的,那么你的调试器可能有点乱。

我已经使用Code::Blocks和Mingw多年了,使用内置调试器(gdb)。只有当我开启优化时才会遇到指针问题,否则它总是知道这个指针并且可以随时引用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接