Visual Studio / MFC 之 bug 搜集(不断更新中)
2008-05-26 21:01:37 旧日重来
Visual Studio 是 Windows 下最优秀和最常用的编程开发工具之一。正如 Windows 一样,Visual Studio 的 bug 也不少,但是微软公司大了,气也粗的很,很多 bug 存在了很久了,就是不见它修正一下。编程工具本身的 bug 显然是令人头疼的,这里搜集了 Visual Studio 和 MFC 的一些 bug,供大家参考。不过需要指出的是,虽然 VS 的 bug 不少,但毕竟是经过了多年考验的东东,实际开发中遇到问题,还是首先怀疑一下自己的代码,由于 VS 的 bug 造成的错误碰上的概率还是非常之小的。
本文不断更新中,最新列表请查看: http://live.aulddays.com/tech/08/vsbugs/
目录:
CHttpFile::QueryInfo() 参数检查错误
存在版本:VS6.0-VS2005;危害程度:大;影响范围:小。
在 MFC CHttpFile 类中有如下函数:
BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, CString& str, LPDWORD lpdwIndex = NULL) const;
根据 MSDN,其中的 dwInfoLevel
参数指明了查询的种类,并且可以包含以下这些
Flags:http://topic.csdn.net/t/20050329/09/3889748.html
HTTP_QUERY_CUSTOM HTTP_QUERY_FLAG_REQUEST_HEADERS HTTP_QUERY_FLAG_SYSTEMTIME HTTP_QUERY_FLAG_NUMBER
可是测试发现,只要 dwInfoLevel
带了以上标志的任何一个就会引发断言错误。而用相同的参数调用该函数的另一个功能基本一样的重载:
BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel,
LPVOID lpvBuffer, LPDWORD lpdwBufferLength,
LPDWORD lpdwIndex = NULL) const
就可以成功。对照这两个函数的源代码的开头部分就可以发现(VC++ 6.0):
BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, CString& str, LPDWORD lpdwIndex) const; { ASSERT(dwInfoLevel <= HTTP_QUERY_MAX && dwInfoLevel >= 0); // 函数的剩余部分 }
BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, LPVOID lpvBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex) const { ASSERT((HTTP_QUERY_HEADER_MASK & dwInfoLevel) <= HTTP_QUERY_MAX && dwInfoLevel != 0); // 函数的剩余部分 }
很显然第一个重载忽视了 HTTP_QUERY_FLAG_REQUEST_HEADERS
等参数的存在,因而造成了误报的断言错误,而第二个版本则考虑更急全面。值得注意的是,在第一个版本中甚至还出现了 dwInfoLevel
>= 0
这样的低级错误,要知道,DWORD
类型是无符号的,因而值总是大于等于 0 的!
直到最新的 VS 中 MS 也没有修正这个 bug。不过
知道了原因,就很容易避免这个bug了。可以选择调用两个重载中正确的那个(它们的功能完全相同,只是传递字符串的方式不同),也可以自己继承 CHttpFile
类并将错误的函数修改成正确的形式。
CToolBar 按钮位置计算 bug
存在版本:VS6.0-VS2005;危害程度:小;影响范围:大。
如下图所示,对于默认的 MFC CToolBar 工具栏(图中的 Original MFC Toolbar),如果把鼠标移到任意一个按钮的最左边一个像素的位置,相应的工具提示和状态栏提示会显示其左边一个按钮的信息,而不是该按钮的信息。
查看 CToolBar 中相关代码的原始版本可以发现(Visual C++ 6.0 版本,新版中的有细微差别,但这个 bug 一直存在):
int CToolBar::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
ASSERT_VALID(this);
ASSERT(::IsWindow(m_hWnd));
// check child windows first by calling CControlBar
int nHit = CControlBar::OnToolHitTest(point, pTI);
if (nHit != -1)
return nHit;
// now hit test against CToolBar buttons
CToolBar* pBar = (CToolBar*)this;
int nButtons = (int)pBar->DefWindowProc(TB_BUTTONCOUNT,
0, 0);
for (int i = 0; i < nButtons; i++)
{
CRect rect;
TBBUTTON button;
if (pBar->DefWindowProc(TB_GETITEMRECT, i, (LPARAM)&rect))
{
++rect.bottom; // Bug 代码行 (旧日重来注)
++rect.right; // Bug 代码行 (旧日重来注)
if (rect.PtInRect(point) &&
pBar->DefWindowProc(TB_GETBUTTON, i,
(LPARAM)&button) &&
!(button.fsStyle & TBSTYLE_SEP))
{
int nHit = GetItemID(i);
if (pTI != NULL && pTI->cbSize >
= sizeof(AFX_OLDTOOLINFO))
{
pTI->hwnd = m_hWnd;
pTI->rect = rect;
pTI->uId = nHit;
pTI->lpszText = LPSTR_TEXTCALLBACK;
}
// found matching rect, return the ID of the button
return nHit != 0 ? nHit : -1;
}
}
}
return -1;
}
在上面的代码中,标记了”bug 代码行“注释的两行就是造成此 bug 的原因。纠正此 bug 的方法就是自己写个继承原 CToolBar
的类,并且重载原始的
CToolBar::OnToolHitTest()
函数,代码和原来的相同,只是去掉错误的两行,并将
CToolBar* pBar = (CToolBar*)this;
这行里的 CToolBar 改成你自己的类名即可。修改后的效果如上图的 Fixed Toolbar。
这个 bug 虽然不起眼,但是影响范围及其广泛,几乎所有用 MFC 写的程序都受到影响。而此 bug 的起源也非常让人捉摸不透。很显然,引起 bug 的两行代码是故意加上的,但是作者又没有给出任何信息为甚么要加上它们。一个貌似合理的解释是 MFC 的作者想给自己的作品加上某种“签名”,这样他们可以很容易的发现哪些程序用了 MFC。另一个相似的例子是 MFC 的状态栏,仔细看的话,MFC 的状态栏比系统默认的状态栏“矮”了一截,而功能上完全不受影响。凭这两样,有经验的人可以从界面上一眼就认出用 MFC 写的程序!
VC++ 6.0 for 循环变量作用域问题
存在版本:VS6.0;危害程度:大;影响范围:大。
在 VC++6.0 中,for 后面循环条件中定义的变量在 for 循环结束后仍然有效,但按照标准 C++ 的规定,循环条件中定义的变量的作用域只限于该 for 循环体中。
严格来说这不能算是一个 bug,而是 VS6.0 发布的时候 C++ 还未标准化,并且在传统 C++ 中对 for 循环变量的解释就是如 VC++6.0 中那样解释的。但是 VS6.0 的这个问题影响相当深远,几乎所有的 VC++6.0 的代码在向 VS 高版本移植时都会遇到此问题。甚至 MS 自己的代码都受到影响:在最新的 DirectShow SDK(包含于 Platform SDK)中,由于引入了一些新特性,该版本的 DShow SDK 不再支持 VS6.0 而只支持高版本的 VS;但富有戏剧性的是 DShow SDK 自带的很多示例代码仍然是按照 VC++6.0 的 for 规范写的,因此在高版本的 VC++ 中反而编译不过,着实让人为 MS 大汗一把……
此问题的具体描述如下:
// VC++ 6.0 for 循环变量作用域问题演示代码 by 旧日重来 void foo() { for(int i = 0; i < 100; ++i) { // Some operation } // 以下这行按标准 C++ 中正确,但 VC++6.0 中语法错 for(int i = 0; i < 100; ++i) { // Some operation } // 以下这行VC++6.0 中正确,但按标准 C++ 语法错 i = 0; }
要写出同时兼容 VC++6.0 和高版本 VC++ 的代码有两个方法,一是把要重复使用的循环变量的定义提到 for 循环条件之前,二是针对 VC++6.0 在需要重复使用循环变量的地方对 for 进行一下“改造”,使之可以“模拟”标准 C++ 的规定:
#ifdef _MSC_VER
#if _MSC_VER == 1200 // 只对 VC++6.0
#define for if(false);else for
#endif
#endif
查看:原文地址;来源:live.aulddays.com。
注意:本站所有文章除特别说明外均为原创,版权所有,转载请务必以超链接方式注明作者出处,并禁止用作商业用途