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

最近在项目上遇到了一个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__ 即可。

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值