为什么即使在调试版本中,VS和Windbg也将“this”指针打印为“0xcccccccc”?

20
我尝试使用windbg在进入成员函数时打印“this”指针,如下所示。

我尝试使用windbg在进入成员函数时打印“this”指针,如下所示。

class IBase {
    int m_i;
public:
    IBase() :m_i(23) {}
    virtual int FuncOne(void) = 0;
    virtual int FuncTwo(void) = 0;
};
class DerivedOne : public IBase {
public:
    virtual int FuncOne(void) { return 1; };//set break point here.
    virtual int FuncTwo(void) { return 2; };
};
class DerivedTwo : public IBase {
public:
    virtual int FuncOne(void) { return 101; };
    virtual int FuncTwo(void) { return 102; };
};
void DoIt(IBase* Base)
{
    int i=Base->FuncOne();//break point here
}
int main(int argc, char *argv[])
{
    DerivedOne d1;
    DerivedTwo d2;
    DoIt(&d1);
    DoIt(&d2);
    return 0;
}

(1) 我使用VC2015调试版(32位)进行了编译。

(2) 我在"DoIt"函数中设置了断点。

(3) 当命中Base->FuncOne()时,我按下"F11"进入DerivedOne函数。

现在我可以看到调用堆栈如下:

0:000> k
 # ChildEBP RetAddr  
00 0041f654 0022157c ConsoleApplication1!DerivedOne::FuncOne [d:\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1.cpp @ 13]
01 0041f734 0022173c ConsoleApplication1!DoIt+0x2c [d:\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1.cpp @ 23]
02 0041f850 00221dc9 ConsoleApplication1!main+0x7c [d:\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1.cpp @ 36]
03 0041f8a0 00221fbd ConsoleApplication1!__tmainCRTStartup+0x199 [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 626]
04 0041f8a8 75b9338a ConsoleApplication1!mainCRTStartup+0xd [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 466]
05 0041f8b4 77529902 kernel32!BaseThreadInitThunk+0xe
06 0041f8f4 775298d5 ntdll!__RtlUserThreadStart+0x70
07 0041f90c 00000000 ntdll!_RtlUserThreadStart+0x1b

但是 "dv" 命令给出了意外的结果。

0:000> dv
       this = 0xcccccccc
为什么会出现这种情况?程序正常运行,在调试版本中没有进行任何优化,看起来一切都很好。但为什么“this”指针无效?
我使用VC的自带IDE进行调试,得到相同的观察结果。但是为什么呢?

4
thisDoit 函数中未定义:它不是你的任何一个类的成员。 - Jean-François Fabre
2
如果你将return 1放在单独的一行上,你会看到一个有效的this - rustyx
就像 RustyX 所说的那样,除非堆栈中有函数调用,否则您无法进入单行函数。至少在 Visual Studio 中是这样。将语句放在单独的行上。 - UmNyobe
1个回答

41
virtual int FuncOne(void) { return 1; };//set break point here.

问题出在你的编程风格上。因为你把函数主体写在了函数定义的同一行,所以断点会被设置在函数的开头,而不是函数主体的开头。此时,函数的序言尚未执行。这段代码设置堆栈框架并检索函数的参数,其中包含隐藏的this参数,作为函数的第一个参数传递。

只有在执行了那个序言代码之后,你才能观察到this具有正确的值。这需要使用调试 > 窗口 > 反汇编,以便你可以跳过序言代码,一直到 mov dword ptr [this],ecx 指令之后的指令。非常麻烦。

当你像这样编写时,就不会出现这个问题:

virtual int FuncOne(void)
{ return 1; };//set break point here.

或者你喜欢的任何大括号风格。现在设置断点可以确保函数的序言已经执行,并且this具有预期的值。

或者,通过了解步进函数并不有趣,因为它没有任何值得调试的内容来解决它。这就是你以那种方式编写它的基本原因。改用“调试”>“跳过当前代码行”而不是单步调试。如果你意外地步入这样的函数,那么使用调试 > 跳出函数快速返回到实际想要调试的代码中。


14
太搞笑了。 :) - Lightness Races in Orbit
但是为什么“单步执行”不进入FuncOne并停在return 1;上呢? - rustyx
9
调试信息基于代码行号。VS支持的其他语言也可以考虑列号。但在C和C ++中由于预处理器的存在,这一点被破坏了,编译器只能看到编译后的代码。该死的预处理器,说实话并不好笑。 - Hans Passant
这只是一行代码,因为他们认为它没有“值得调试的东西”——但通常会调试自己认为错误的内容。有时,“步入表达式”(或“语句”)会很方便! - PJTraill
太好了,简直不敢相信。棒极了。 - alecov

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