我现在做的系统有的时候会出现这样的断言失败:
Debug Error!
DAMAGE: after Normal block (#328Array) at 0x182C30F0.
跟踪一下,发现问题竟出在CString的析构函数中,于是拿出了大半天的时间来研究这个问题,终于发现了原因所在。
问题的起因是我像下面这样调用无参的构造函数声明一个CString对象:
CString strText;
然后把它以这样的方式传递给别的函数:(函数1)
pVCG->GetRotDirection(WAVE_P, m_nWaveSide, strText.GetBuffer(0));
而在这个函数里对于字符串指针进行了类似于如下的操作:
sprintf(strDir, "%s", "CW");
这样做的危险性在于当字符串没有被初始化的时候,CString内部指向缓冲区的指针指向的是一个随机的地址,在CString的无参构造函数调用
了如下函数:
_AFX_INLINE void CString::Init()
{ m_pchData = afxEmptyString.m_pchData; }
m_pdhData的定义:LPTSTR m_pchData;
afxEmptyString的定义是:
#define afxEmptyString AfxGetEmptyString()
const CString& AFXAPI AfxGetEmptyString()
{ return *(CString*)&_afxPchNil; }
_afxPchNil的来源如下:
AFX_STATIC_DATA int _afxInitData[] = { -1, 0, 0, 0 };
AFX_STATIC_DATA CStringData* _afxDataNil = (CStringData*)&_afxInitData;
AFX_COMDAT LPCTSTR _afxPchNil = (LPCTSTR)(((BYTE*)&_afxInitData)+sizeof(CStringData));
从上面的代码可以看出,没有进行初始化操的CString对象它们的缓冲区指针都是指向一块相同的内存:和一个全局数组相关的地址。
而在函数1例调用sprintf修改CString对象的缓冲区的结果是修改所有未初始化CString内部缓冲区指针所指,这么做是非常危险的。但是这还不是出现断言错误的原因。
接下来的错误,更难被发现。接着我的程序又调用了两次类似于下面的函数(函数2)
pVCG->GetCompressionGrade(WAVE_QRS, m_nWaveSide, 0, 60, 0, 0, strText);
在这个函数的内部有str.Format(IDS_COMPRESSION_LESS);这样的操作。
这是MFC里CString::Format的相关代码:
void AFX_CDECL CString::Format(UINT nFormatID, ...)
{
CString strFormat;//没有直接修改自己,而是先对新声明的字符串进行操作
VERIFY(strFormat.LoadString(nFormatID) != 0);
 
va_list argList;
va_start(argList, nFormatID);
FormatV(strFormat, argList);
va_end(argList);
}
而在void CString::FormatV(LPCTSTR lpszFormat, va_list argList)里最后作如下操作:
GetBuffer(nMaxLen);
VERIFY(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength());//将修改后的字符串拷贝到自己的缓冲区内
ReleaseBuffer();
关键在GetBuffer:
LPTSTR CString::GetBuffer(int nMinBufLength)
{
ASSERT(nMinBufLength >= 0);
 
if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
//如果指定的内存空间比已经分配的空间小的话,则重新分配,并释放掉原来的内存
{
#ifdef _DEBUG
        // give a warning in case locked string becomes unlocked
        if (GetData() != _afxDataNil && GetData()->nRefs < 0)
               TRACE0("Warning: GetBuffer on locked CString creates unlocked CString!\n");
#endif
// we have to grow the buffer
       CStringData* pOldData = GetData();
        int nOldLen = GetData()->nDataLength;   // AllocBuffer will tromp it
        if (nMinBufLength < nOldLen)
               nMinBufLength = nOldLen;
       AllocBuffer(nMinBufLength);
       memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR));
       GetData()->nDataLength = nOldLen;
       CString::Release(pOldData);
}
ASSERT(GetData()->nRefs <= 1);
 
// return a pointer to the character storage for this string
ASSERT(m_pchData != NULL);
return m_pchData;
}
由于字符串没有被初始化,所以GetData()->nAllocLength=0,因此if语句块被执行,重新在堆上分配内存,销毁原来的内存,这才第一次给字
符串分配内存。
这时还不会出现问题,接下来还会执行类似函数1的操作。
最后问题之所以发生在CString被析构的时候,原因就在于,在执行函数2的时候,字符串有了能容纳4个字节的缓冲区.如果调试的时候打开Memory窗口,在Address:文本框里输入一个堆内存的地址,可以发现VC在调试版的程序里为每个在堆里分配的内存块的后面加了4个字节的内容,值全为FD,用于检查内存越界。CString析构的时候,调用了调试版的operator delete,它就以此为依据进行了内存检测:
if (!CheckBytes(pbData(pHead) + pHead->nDataSize, _bNoMansLandFill, nNoMansLandSize))
_RPT3(_CRT_ERROR, "DAMAGE: after %hs block (#%d) at 0x%08X.\n",
                    szBlockUseName[_BLOCK_TYPE(pHead->nBlockUse)],
                    pHead->lRequest,
                    (BYTE *) pbData(pHead));
由于后来再次调用的函数1时它产生的长度有的时候会大于4 ,就破坏了后面的边界,所以会出现这样的问题。
出现这种问题时,在调试状态下会在输出窗口输出如下类似信息:
memory check error at 0x182C7F22 = 0x57, should be 0xFD
结论:
1.所以str.GetBuffer(0)作为参数传递的时候适合于作为只读的参数;
2.如果非得要做可以修改的参数,那就得给GetBuffer传递一个保证足够安全的参数,也就是足够大;
2.如果调试版的程序出现类似
Debug Error!
DAMAGE: after Normal block (#328Array) at 0x182C30F0.
的错误,应想到内存冲突。
问题终于水落石出了。反思一下,这个问题一点也不难,都怪自己基础没有打好,考虑问题不周全。

CString对象的一种错误的使用方式相关推荐

  1. 花呗使用别踩雷区!这几种错误的使用方式,很容易导致花呗被关闭

    支付宝的出现方便了我们的生活,现在很多人都习惯使用支付宝进行线上和线下的消费支付.虽然微信也有支付功能,但是微信主要还是属于社交领域,相比之下支付宝就显得更加商业化.正因为如此,支付宝中才会有花呗和借 ...

  2. 微博运营与微博营销最易犯的20种错误,你犯了吗?

    微博估计是自搜索引擎以来迅速被企业应用最广泛的网络营销工具.而且还免费.但做了不意味着有效.在企业微博运营与微博营销过程中,你或许正在犯着这样那样的错误,本文以新浪微博为例,汇总微博运营与微博营销最易 ...

  3. 创建函数查找上级_一文秒懂JavaScript中对象的7种创建方式

    1.工厂模式 javascript 代码 工厂模式:能根据接受的参数来创建出一个person对象.也可以无数次的调用这个函数,每次都会返回一个包含3个属性和1个方法的对象. 工厂模式虽然解决了创建多个 ...

  4. 获取Class对象的三种方式

    获取Class对象的三种方式 Object --> getClass() 通过对象.getclass 任何数据类型(包括基本数据类型)都有一个"静态"的class属性 通过类 ...

  5. django models索引_Django开发者常犯的7种错误

    Django是一个强大的web框架,但是它的强大也带来了责任.在本文中,我们将讨论即使是经验丰富的Django开发人员也会犯的常见错误,但是大多数成功的Django项目迟早都需要处理这些错误. 重新发 ...

  6. Java中对象的三种状态

    转载自   Java中对象的三种状态 Java中的对象的三种状态是和垃圾回收紧密相关的,因此有必要深究. 状态一:可触及态:从根节点开始,可以搜索到这个对象,也就是可以访问到这个对象,也有人将其称为可 ...

  7. java 开发人员工具_Java开发人员应该知道的5种错误跟踪工具

    java 开发人员工具 随着Java生态系统的发展,可满足不断增长的请求和用户对高性能需求的Web应用程序成为了新型的现代开发工具. 具有快速新部署的快速节奏环境需要跟踪错误,并以传统方法无法维持的水 ...

  8. Java开发人员应该知道的5种错误跟踪工具

    随着Java生态系统的不断发展,可满足不断增长的请求和用户对高性能需求的Web应用程序成为了新型的现代开发工具. 具有快速新部署的快速节奏环境需要跟踪错误并获得应用程序行为的洞察力,而传统方法无法维持 ...

  9. asp 开发app_ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式

    由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...

最新文章

  1. python+OpenCV图像处理
  2. js如何使浏览器允许脚本异步加载
  3. python的用途-python“ with”语句的用途是什么?
  4. django python3.6_Django+mysql+python3.6.5 Windows
  5. pthread_join函数
  6. c语言交通违章编程代码,C语言程序设计之交通处罚单管理系统 报告(内含代码).doc...
  7. JDK JRE 区别
  8. APP设计灵感|仪表盘这样设计,所有信息一目了然!
  9. [tensorflow]tensorflow2.0的优化理论
  10. 进阶篇第九期:相册与拍照的后处理
  11. 网络基础——网络层(ip协议详解)
  12. 市场调报告—2021-2027中国非霍奇金淋巴瘤和慢性淋巴瘤治疗市场现状及未来发展趋势
  13. 【uniapp】上传体验版的过程,解决上传时体积过大的问题
  14. altium designer常用元件电气符号和封装形式
  15. 接线-继电器和USB线
  16. 淘宝运营思路彻底转变,从人货场到AIPL模型
  17. 点击按钮打开windows计算器,python代码示例,有GUI界面,直接写代码
  18. 职业3D游戏建模师入行工资一个月是多少钱?
  19. 5R(康奈尔)笔记法
  20. 怎么在WEB页面上读取身份证信息

热门文章

  1. python numpy库安装winerror5_(转载)Numpy安装中遇到的问题和解决方法
  2. Keras之DNN:利用DNN算法【Input(8)→12+8(relu)→O(sigmoid)】利用糖尿病数据集训练、评估模型(利用糖尿病数据集中的八个参数特征预测一个0或1结果)
  3. 关于Jdk7与Jdk8对Collections进行分组的区别
  4. select 实现server I/O多路复用通信
  5. 如何使用IcoMoon字体图标
  6. Win32汇编环境搭建教程(MASM32 SDK)
  7. css中使用id和class 的不同
  8. ACM中java的使用 (转)
  9. (线段树)Just a Hook -- hdu -- 1689
  10. shell处理mysql增、删、改、查