VC++ MFC常见异常问题及处理方法
一、内存管理异常
1. 野指针访问(崩溃弹框)
// 错误示例:删除指针后未置空
CString* pStr = new CString(_T("Test"));
delete pStr; // 释放后未置空
// pStr = nullptr; // 正确操作应加这行
CString str = *pStr; // 访问野指针,触发异常
解决:释放内存后立即置空指针。
2. 堆数组越界(堆破坏弹框)
// 错误示例:动态数组访问越界
int* pArr = new int[5];
pArr[5] = 10; // 越界访问,破坏堆结构
delete[] pArr;
解决:用 sizeof 或变量记录数组长度,遍历前校验索引。
3. 内存泄漏(无弹框但性能下降)
// 错误示例:new后未delete
void OnButtonClick() {
CMyClass* pObj = new CMyClass(); // 未释放
}
解决:用智能指针 CComPtr 或RAII模式管理资源。
二、界面与控件异常
4. 控件句柄无效(断言 ASSERT(::IsWindow(m_hWnd)) )
// 错误示例:销毁控件后访问句柄
void CMyDlg::OnDestroy() {
CDialogEx::OnDestroy();
m_EditCtrl.DestroyWindow(); // 销毁控件
// m_EditCtrl.SetWindowText(_T("Text")); // 后续调用会触发断言
}
解决:销毁控件后设置句柄为 NULL : m_EditCtrl.m_hWnd = NULL; 。
5. 对话框数据交换(DDX/DDV)失败
// 错误示例:控件ID与变量绑定错误
DDX_Text(pDX, IDC_EDIT_WRONG, m_WrongVar); // IDC_EDIT_WRONG实际应为IDC_EDIT_CORRECT
DDV_MaxChars(pDX, m_WrongVar, 10); // 数据校验失败触发弹框
解决:检查 .rc 文件中控件ID与 DoDataExchange 函数是否一致。
6. 模态对话框阻塞主线程(无弹框但界面假死)
// 错误示例:在耗时操作中创建模态对话框
void CMyDlg::OnLongTask() {
// 模拟耗时操作
for (int i = 0; i < 1000000; i++) {}
CMyChildDlg dlg;
dlg.DoModal(); // 阻塞主线程,界面无法响应
}
解决:将耗时操作放到工作线程( AfxBeginThread )中执行。
三、文件与IO异常
7. 文件未找到( CFileException )
// 错误示例:硬编码路径导致文件不存在
try {
CFile file;
file.Open(_T("C:\\不存在的文件.txt"), CFile::modeRead); // 触发异常
} catch (CFileException* e) {
e->ReportError(); // 弹出文件未找到对话框
e->Delete();
}
解决:先检查文件是否存在: CFile::GetStatus(path, status) 。
8. 磁盘满导致写入失败
// 错误示例:未处理写入异常
CFile file;
file.Open(_T("large_file.dat"), CFile::modeCreate | CFile::modeWrite);
// 模拟写入大文件(超过磁盘剩余空间)
char buffer[1024*1024];
file.Write(buffer, sizeof(buffer)); // 可能触发异常
解决:用 try-catch 捕获 CFileException ,检查错误码 e->m_cause == CFileException::diskFull 。
四、多线程与同步异常
9. 线程间非法访问UI控件(崩溃)
// 错误示例:工作线程直接操作UI控件
UINT WorkThread(LPVOID pParam) {
CMyDlg* pDlg = (CMyDlg*)pParam;
pDlg->m_EditCtrl.SetWindowText(_T("Thread")); // 跨线程访问,未加同步
return 0;
}
解决:通过 PostMessage 或 SendMessage 向主线程发送消息更新界面。
10. 临界资源竞争(断言 AfxLockTempMap() )
// 错误示例:多线程同时操作全局CMap对象
CMap<int, int, CString, CString&> g_Map;
UINT Thread1(LPVOID) { g_Map.SetAt(1, _T("A")); return 0; }
UINT Thread2(LPVOID) { g_Map.SetAt(2, _T("B")); return 0; }
// 同时调用导致临时映射表竞争
解决:用 CCriticalSection 保护共享资源:
CCriticalSection g_CS;
UINT Thread1(LPVOID) {
g_CS.Lock();
g_Map.SetAt(1, _T("A"));
g_CS.Unlock();
return 0;
}
五、数据库与网络异常
11. ODBC连接失败( CDBException )
// 错误示例:错误的数据库连接字符串
CDatabase db;
try {
db.Open(_T("DSN=WrongDSN;UID=sa;PWD=123")); // DSN不存在
} catch (CDBException* e) {
e->ReportError(); // 弹出ODBC错误对话框
e->Delete();
}
解决:检查ODBC数据源名称(DSN)、用户名和密码是否正确。
12. 网络连接超时( CInternetException )
// 错误示例:未设置超时时间
CInternetSession session;
CHttpFile* pFile = NULL;
try {
pFile = (CHttpFile*)session.OpenURL(_T("http://www.baidu.com"), 1,
INTERNET_FLAG_RELOAD, _T(""), _T(""), 0);
} catch (CInternetException* e) {
DWORD dwError = e->m_dwError;
if (dwError == INTERNET_ERROR_TIMEOUT) {
AfxMessageBox(_T("连接超时!"));
}
e->Delete();
}
解决:通过 SetOption 设置超时时间:
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 5000); // 5秒超时
六、自定义异常与断言
13. 自定义断言失败( ASSERT 弹框)
// 错误示例:参数校验失败
void Func(int nValue) {
ASSERT(nValue > 0); // nValue为0时触发断言弹框
// ...
}
解决:确保输入参数合法,或用 ATLASSERT 在Release版禁用断言。
14. 未捕获的C++异常(程序终止弹框)
// 错误示例:抛出异常未捕获
void Func() { throw _T("Error"); }
void CMyDlg::OnButtonClick() {
Func(); // 未用try-catch捕获,导致程序终止
}
解决:在函数调用层添加异常处理:
try { Func(); } catch (CString str) { AfxMessageBox(str); }
七、其他典型问题
15. 菜单ID重复(运行时崩溃)
// 错误示例:多个菜单项使用相同ID
// 在资源编辑器中,菜单项“文件-打开”和“编辑-打开”均设为ID_FILE_OPEN
解决:确保所有菜单项ID唯一。
16. 资源句柄泄漏(如图标、画笔)
// 错误示例:创建GDI对象后未释放
void OnPaint() {
CPaintDC dc(this);
CBrush brush(RGB(255, 0, 0));
dc.FillRect(CRect(0,0,100,100), &brush);
// brush.DeleteObject(); // 未释放画笔,导致资源泄漏
}
解决:使用完GDI对象后立即调用 DeleteObject 。
快速调试技巧
1. 开启断言:在 stdafx.h 中确保定义 _DEBUG 宏。
2. 使用调试工具:
- 断点调试:在 ASSERT 或崩溃位置设断点,查看调用堆栈。
- 内存调试:添加 #define _CRTDBG_MAP_ALLOC 和 _CrtDumpMemoryLeaks() 检测泄漏。
3. 错误码查询:通过 GetLastError() 获取系统错误码,用 FormatMessage 转换为可读信息。