VC开发中CString,std::string的错误使用

在软件中,CString,std::string方便性,使其使用较为广泛,但是其中的坑也是不少,本文主要介绍其结构,并举例(均是项目中产生的BUG,,排查过程一片哀鸿)。

CString的大小只有四个字节,指向堆上的一个结构地址,CString结构[CStringData结构][存储字符串],所以CString所存的内容,均在堆上,CString内容的增长以动态增加分配的方式,后面无足够的空间进行分配,则会重新分配内存,CString的地址随之改变。

std::string大小占32个字节,内存结构如下:// f8 6a 90 00 (base地址:用于字符串运算操作,如字符交换等)31 32 33 00 cc cc cc cc cc cc cc cc cc cc cc cc(默认数组空间) 03 00 00 00 (字符串大小变量)0f 00 00 00 (字符串容量大小变量)cc cc cc cc(预留使用)。当实std::string的内存小于默认的16个字节,则内容都存储在该内容数组中,如果大于16个字节,则会在堆上申请内存来存放内容,该内容数组则存储堆上内容的地址。

下面贴项目中错误事例,以防大家再挖坑:

void CStringDumpDlg::OnBnClickedAbort()
{
// TODO: 在此添加控件通知处理程序代码
/*IDC_EDIT1指向一个输入编辑框
1,edit 为空,点击getwindowtext按钮,不会跳叉,原因cstring指向内容的地址默认有各个字节
放空字符,而edit为空,不会影响到cstring的内存结构,所以不会跳叉
2,edit有一个及以上的字符,点击getwindowtext按钮,跳叉,即时默认有一个字节的长度,也不会
影响到cstring的内存结构,但是违背了结束符必须是空字符的原则,所以也会跳叉,如果edit输入多个
字节,直接破坏了内存结构造成跳叉,通过内存窗口查看,其0xfdfdfdfd直接被改写。
造成cstring的析构函数释放内存时报错。
*/

CString csTmp;
void* pBuffer = csTmp.GetBuffer();
GetDlgItem(IDC_EDIT1)->GetWindowText(csTmp.GetBuffer(),100);

char chArray[100] = {0};
GetDlgItem(IDC_EDIT1)->GetWindowText(chArray,100);

int abc = 90;
abc++;
}


void CStringDumpDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
/*
cstring存放内容,通过动态增加分配的的方式,如果cstring需要新增内容,
如果原先存放内容的指针所指向的内存空间足够,
则调用realloc向后增加内存空间,不够则整个cstring则重新拷贝,
所以慎用getbuffer,有可能下次使用的时候,该指针已经是野指针。示例如下
*/
CString csTmp;
TCHAR* pBuffer = csTmp.GetBuffer();
csTmp = "supcon";
for (int i = 0;i<1;i++)
// for (int i = 0;i<100;i++)
{
CString csIndex;
csIndex.Format("%d",i);
csTmp += csIndex;
}

CString csTmp2(pBuffer);

int abc = 90;
abc++;
}

std::string s_strName = "liuqihe123456789012345";

void CStringDumpDlg::OnBnClickedButton4()
{
// TODO: 在此添加控件通知处理程序代码
//  std::string和memcpy,慎用。
//  下一行是std::string的内存结构
// f8 6a 90 00 (base地址:用于字符串运算操作,如字符交换等)31 32 33 00 cc cc cc cc cc cc cc cc cc cc cc cc(默认数组空间) 03 00 00 00 (字符串大小变量)0f 00 00 00 (字符串容量大小变量)cc cc cc cc(预留使用)
//  std::string的存储方式是栈上一个
//  数组(由于编译器和电脑32位64位的区别,这个数据的长度不定,基本上是16字节及其倍数),
//  当字符串大小大于栈数组大小时,重新分配堆上的空间给string使用,则默认数组空间存储的就不是字符串内容,而是字符串的地址。
std::string strName;
std::string strName2;

//  此处的内存拷贝操作,不会报错,由于字符串长度小于16,所以内存都在默认数据空间中,
//  所以函数执行完成调用strName和strName2的析构函数不会调差,
strName = "supcon";
memcpy(&strName2,&strName,sizeof(strName));
// 
//  此处的内存拷贝操作,不会报错,但是实际上并没有进行字符串的拷贝,拷贝的是字符串指针,
//  当函数执行完成会调用strName和strName2的析构函数,从而造成strName2的析构函数去释放strName1中已经释放的内容,则跳叉。
  strName = "supcon12345678901234";
  memcpy(&strName2,&strName,sizeof(strName));

//  该代码已经破坏了strName2的内存结构,会造成何种结果未可知,主要受strName的长度和内容所影响。
//  strName = "supcon123456789"; // 方式一
//  strName = "supcon123456789123456789"; // 方式二
//  memcpy((void*)strName2.c_str(),(void*)strName.c_str(),strName.length()+1);
//  std::string strName3(strName2);


int abc = 90;
abc++;
}

CString g_csName = "supcon123";
CString g_csName2(g_csName);
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
// CString非线程安全,csPath从野指针拷贝数据,造成内容出错
CString csPath=(LPCSTR)lpParam;

// CString的内存结构是四个字节,存储的是字符串内容地址,该字符串内容存放在堆上,
// 调用ExitThread造成CString无法调用析构函数进行释放,造成内存泄露.
//  CString csName = "supcon"; 
// csNameThread和g_csName共享字符内存块,调用ExitThread造成字符串"supcon123"的引用计数出错,则内存已乱
//CString csNameThread(g_csName);
//  ExitThread(0);

return 0;
}

std::string g_strN = "123456";
void CStringDumpDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码

CString csName("supcon");
DWORD dwThreadID = 0;
HANDLE hThread=::CreateThread(NULL,NULL,ThreadFunc,(LPVOID)(LPCTSTR)csName.GetBuffer(),0,&dwThreadID);
CloseHandle(hThread);

//CString有个功能是“写入复制技术(CopyBeforeWrite)”。
//当使用一个CString对象A来初始化另外一个CString对象B时,B并不会被分配空间,
//而是将自己的指针指向对象A的存储空间。除非对两个中的某个做修改时,才会为对象B申请内存。
//CString结构[CStringData结构][存储字符串]
CString str(g_csName);
CString a = str;
CString b(str);
CString c;
c = b;
//对比
std::string str1(g_strN);
std::string str2 = g_strN;
std::string str3;
str3 = str2;
//当多个对象共享同一块内存时,这块内存就属于多个对象,
//而不在属于原来的申请这块内存的那个对象了。但是,每个对象在其生命结束时,
//都首先将这块内存的引用减一,然后再判断这个引用值,如果小于等于零时,就将其释放,
//否则,将之交给另外的正在引用这块内存的对象控制。
int dd = 90;
}
#include "IInterfaces.h"

void CStringDumpDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
HMODULE hModule = LoadLibrary("StringDmpDll.dll");


fp_GetISAMSClientPlugin fp_GetIPlugin = (fp_GetISAMSClientPlugin)GetProcAddress(hModule,"GetISAMSClientPlugin");

if (!fp_GetIPlugin)
{
return;
}

IInterfacePlugin* pInterface = fp_GetIPlugin();


if (!pInterface)
{
return;
}


// 本例中出现问题,主要是运行库的静态调用方式造成的,选择运行库静态调用 ,
// 则主程序和DLL中各自维护自己的堆,new和delete操作应保持在一个堆中执行,否则抛堆异常。
// 选择运行库共享调用,则DLL中使用主程序的堆,则不存在new和delete在两个堆中执行的情况。
// 注:一般来说,DLL中的堆比主程序的堆要小很多,尽量不要在DLL中new过大的内存。
pInterface->ExtraFunctionInt(3);

// C++标准库里提供的类直接或间接地使用了静态变量。由于这些类是通过模板扩展而来的,因
//此每个可执行映像(通常是.dll或.exe文件)就会存在 一份只属于自己的、给定类的静态数据成员。
//当一个需要访问这些静态成员的类方法执行时,它使用的是“这个方法的代码当前所在的那份可执行映像”
//里的静态成 员变量。由于两份可执行映像各自的静态数据成员并未同步,这
//个行为就可能导致访问违例,或者数据看起来似乎丢失或被破坏了。
// 所以在接口函数中,尽量不要使用标准库中类对象做参数,stl,string等,以类指针代替。


//问题的出现,基本上是在静态调用运行库,造进程中多个堆,具体原因就是/MT  /MD的区别,大家自可百度。

std::string strTmp = "123";
pInterface->ExtraFunctionString(strTmp);

//std::string strLong = pInterface->ExtraFunction();

std::vector vecInt;
vecInt.push_back(1);
vecInt.push_back(2);
pInterface->ExtraFunctionIntVec(vecInt);
std::vector vecStr;
vecStr.push_back("supcon");
vecStr.push_back("supcondcs");
pInterface->ExtraFunctionStringVec(vecStr);

int abc = 90;
}

你可能感兴趣的:(VC开发中CString,std::string的错误使用)