案例
最近在做一个Windows程序,其中有个消息处理函数,大概是这样的:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
ON_MESSAGE(WM_MY_MESSAGE, &CMainFrame::OnMyMessage)
END_MESSAGE_MAP()
LRESULT CMainFrame::OnMyMessage(WPARAM wp, LPARAM lp)
{
char sBuf[2048];
…….省略其它处理
SendMessage(WM_ANONYTHER_MSG, sBuf, 0);
}
在OnMyMessage中省略了其它代码,只保留了两个关键的地方。一个为其内部定义了一个局部变量sBuf,其大小为2K,另一个为调用SendMessage发送另一个消息。
测试中发现堆栈溢出的现象,而崩溃的地方就在OnMyMessage里。这个现象比较奇怪,因为OnMyMessage并没有其它地方有直接的函数调用,只有这一个消息响应的入口。而发送消息的地方也都是在其它线程里通过SendMessage发送的。而SendMessage是同步消息,要等到该消息处理完成后才会返回。因此不应该出现函数重入的情况。
分析
通过观察调用栈发现,OnMyMessage确实重入了多次,而其中的局部变量又比较大,从而导致了堆栈溢出。
那么问题来了,既然OnMyMessage没有递归调用,那么为什么会像递归调用一样被重入了多次呢?原因就出在OnMyMessage里调用了SendMessage(WM_ANONYTHER_MSG)。
实际上SendMessage之后,在主线程阻塞等待WM_ANONYTHER_MSG响应时,还是可以再继续处理消息队列上的其它消息的。而如果此时消息队列上有大量的WM_MY_MESSAGE,而WM_ANONYTHER_MSG的响应又确实比较慢时,那么主线程就会不断的处理WM_MY_MESSAGE,从而重复的调用OnMyMessage,造成了一种类似递归调用的现象。最终导致堆栈溢出。
此问题的修改方法也比较简单,只需将OnMyMessage里的局部变量改为动态申请即可:LRESULT CMainFrame::OnMyMessage(WPARAM wp, LPARAM lp)
{
char* sBuf = new char[2048];
…….省略其它处理
SendMessage(WM_ANONYTHER_MSG, sBuf, 0);
}
小结
1. 编程时尽量避免显示递归调用。如果确实需要用到显示递归调用,需保证局部变量不能太大。
2. 需注意一些可能会发生隐式递归调用的地方(比如Windows编程中的消息响应函数),其中也避免有太大的局部变量。