记一个C++中的疑难杂症

来源:互联网 发布:服务器php环境配置 编辑:程序博客网 时间:2024/05/17 04:55
最近在项目上遇到了一个C++中常见的疑难杂症。有些令人哭笑不得的感觉。先不多说,看下文。

有两个Project -- A 和 B. A会被编译链接成为一个dll文件,而B会生成一个exe文件。
在Project A中有一个类 C 和 一个类 D,如下:
class D {......public:    void d_f1() {        // do not access any member variables here    }    void d_f2() {        d_f1();        m_value = 100;        ......    }......private:    int m_value;    ......};class C {......public:    C() {        #ifdef __XXX__            p = new D;        #endif    }        ~C() {        #ifdef __XXX__            delete p;        #endif    }    void c_f() {        #ifdef __XXX__            p->d_f2();        #endif    }......private:    ......        #ifdef __XXX__      D* p;    #endif    ......};

在Project B中有一个类 X 及其成员函数 f(), 其定义大约如下:
class X {......public:     X() { C m_var; }public:    void f() {        m_var.c_f();        int a = 100;        .....    }    ......private:     C m_var;    ......};

具体现象如下:
X x;x.f();  // 一切正常...x.f();  // 程序崩溃

因为上文已经将本问题极大地浓缩,相信聪明而经验丰富的你或许已经看出了问题所在。。。没看出来?!没关系,我们来debug一下:)不过实在要吐槽的是,本问题中的工程都是QT中的工程,而QT的Debugger虽然选的仍是微软的CDB,但是那个慢呀!按一下F10,至少要等半分钟才有反应,有时甚至好几分钟。所以不到万不得已,打log都比debug快得多。
在debug过程中,笔者发现,第2次进入f()以后,或者说第2次进入d_f2()后,仍然把d_f1()执行了,但是再接下去执行"m_value = 100;"的时候,程序就崩溃了。于是,问题来了:
1. 为什么第一次执行的时候不崩溃,而第二次就崩溃?
2. 为什么崩溃前还能把d_f1()执行了?

其实真正去做过debug就可以看出来,在访问到m_value的时候,已经显示对m_value的访问是非法的了!这就是崩溃的原因了。至于为何崩溃前仍能把d_f1()执行了,那是因为在d_f1()这个函数内并没有访问任何D的成员变量,而对于函数指针d_f1的访问,并没有涉及到访问其他人的内存。这就是第二个问题的答案了。
可是,第一个问题还没有回答,为何第一次没有崩溃呢?

因为第一次调用f()的时候,m_value这块内存是自己的;而第二次调用f()的时候,这块内存不是自己的了。

好吧,说了等于没说。为何第一次的时候是自己的,第二次就不是自己的了呢?


其实,Root Cause是在于,在Project A中是定义了宏 __XXX__ 的,而在Project B中并没有定义宏 __XXX__. 因为B.exe链接了A.dll,所以在第一次调用x.f()的时候,进入了A.dll的代码空间中,这里是存在宏 __XXX__,因此,对于 m_var 这个类 C 的实例,此时是认为其内存布局中是存在 D* p 的,于是便调用了 p->d_f2()。 此时因为是程序的早期,在d_f2()中对于 m_value 的访问并没有引发什么问题。(这里,笔者仍然是有疑问的:此时的d_f2()里的m_value所占用的内存空间到底是不是m_var的?似乎不是,但也没出错。)

但是,因为x这个实例是在栈上的,所以Project B中的 m_var 这个实例并不知道  __XXX__,所以 m_var 的内存空间中并没有 D* p. 而在第一次 m_var.f() 调用完成后,"int a = 100;" 等后续语句就会继续使用栈空间,即 int a = 100; 及其后续语句就会把原先存放 D* p 的栈空间占用掉,所以等到第二次执行x.f()时,即进入A.dll的代码空间时,会去访问不是 x 实例自己空间以试图访问 m_value,但因为那其实是其他实例(比如 int a)的空间,所以导致了程序崩溃。

修复的方法不言自明了, 只要在Project B中也加入宏 __XXX__ 即可。

(完)
1 0
原创粉丝点击