关于C语言字符串函数的思考
副标题#e#
C语言并不是一种很利便的语言,它的字符串就是一例。凭据C语言的界说,“字符串就是一段内存空间,内里包括ASCII字符,而且,以“\0”末了,总共能存放n-1个字符。”凭据这个描写,字符串处理惩罚确实很贫苦,还很容易堕落。
为了利便用户,C语言尺度库向用户提供了一些字符串函数,如字符串拷贝、结构、清空等函数,在必然水平上利便了用户的利用。可是,我无意中发明,这些函数照旧有些隐患的。
工作很简朴,我留意到我写的一些措施,总是有内存读写错误,可是,颠末仔细查抄我所有的数据Buffer,以及相关的处理惩罚函数,又没有找到什么错误。于是我把猜疑的眼光投向我常用的一些字符串处理惩罚函数上,如strcpy、sprintf等。在颠末屡次仔细地跟踪之后,我发明内存错误出自于此。于是,我开始研究如何安详地利用字符串这个话题。
1.字符串拷贝函数
1.1 不安详的strcpy
首先,我写了这样一个测试函数:
void strcpyTest0()
{
int i;
char szBuf[128];
for(i=0;i<128;i++) szBuf[i]='*';
szBuf[127]='\0'; //结构一个全部是*的字符串
char szBuf2[256];
for(i=0;i<256;i++) szBuf2[i]='#';
szBuf2[255]='\0'; //结构一个全部是#的字符串
strcpy(szBuf,szBuf2);
printf("%s\n",szBuf);
}
很简朴,把一个字符串拷贝到别的一个空间,可是,很不幸,源字符串例如针地点要长,因此,措施很悲凉地死去了。
1.2 照旧不安详的strncpy
通过上例,我发明我需要在拷贝时多输入一个参数,来标明目标地点有多长,查抄C语言的库函数说明,有一个strncpy可以到达这个目标,这个函数的原型如下:
char *strncpy( char *strDest, const char *strSource, size_t count );
好了,这下我们的问题办理了,我写出了如下代码:
void strcpyTest1()
{
int i;
char szBuf[128];
for(i=0;i<128;i++) szBuf[i]='*';
szBuf[127]='\0';
char szBuf2[256];
for(i=0;i<256;i++) szBuf2[i]='#';
szBuf2[255]='\0';
strncpy(szBuf,szBuf2,128);
printf("%s\n",szBuf);
}
一切都显得很好,可是,当我输出功效的时候,发明白问题,字符串后头有时会跟几个奇怪的字符,仿佛没有用“\0”竣事,于是我把上面的拷贝语句改成“strncpy(szBuf,szBuf2,8);”,只拷贝8个字符,问题呈现了,措施输出如下:
########***********************************************************************************************************************
公然,当请求的方针地点空间比源字符串空间要小的时候,strncpy将不再用“\0”来竣事字符串。庞大的隐患。
#p#副标题#e#
1.3 安详地字符串拷贝函数
我仔细想了想,我认为我需要如下一个字符串拷贝函数:
1、答允用一个整数界定方针地点空间尺寸。
2、当方针地点空间nD小于源字符串长度nS时,应该只拷贝nD个字节。
3、任何环境下,方针地点空间均应该以“\0”竣事,保持一个正当的字符串身份。因此,获得的字符串最大长度为nD-1.
于是,我写了这么一个字符串拷贝函数:
void xg_strncpy1(char *pD, char *pS,int nDestSize)
{
memcpy(pD,pS,nDestSize);
*(pD+nDestSize-1)='\0';
}
很EASY是不,将这个拷贝函数代入上面的例子,只输出7个“#”, 功效正确。
1.4 内存读错误的思考
原来觉得可以就此打住了,不外,没多久,我就发明一个奇怪的现象,这个函数在VC的Debug模式下有错误,可是Release模式下却一切正常。
我奇怪了好久,终于有一天我忍不住了,抉择办理这个问题,我把上面的memcpy用本身的一个复制轮回取代,单步跟踪,想看看毕竟怎么回事?
原因找到了,我但愿拷贝一个256字节长的字符串,可是,拷贝到第33字节时堕落,查抄措施,发明我的源字符串空间只有32 Bytes,本来,我上面的代码只是防备了内存写出界,但没有针对读出界举办查抄,在VC的Debug模式下,内存读出界也是一种犯科错误,因此被报错。
知道了原因,办理就很简朴了,我把上面的拷贝函数改成如下形状:
#p#分页标题#e#
void xg_strncpy2(char *pD, char *pS,int nDestSize)
{
int nLen=strlen(pS)+1;
if(nLen>nDestSize) nLen=nDestSize;
memcpy(pD,pS,nLen);
*(pD+nLen-1)='\0';
}
一切OK.
2.字符串结构函数
2.1 不安详的sprintf
如同上例,我在修改拷贝函数的同时,我也想到了别的一个我常用的字符串结构函数sprintf,显然,这个函数没有界定方针地点空间的尺寸,也是不安详的,下面的代码将会造成瓦解:
void sprintfTest0()
{
int i;
char szBuf[128];
for(i=0;i<128;i++) szBuf[i]='*';
szBuf[127]='\0';
char szBuf2[256];
for(i=0;i<256;i++) szBuf2[i]='#';
szBuf2[255]='\0';
sprintf(szBuf,szBuf2);
printf("%s\n",szBuf);
}
2.2 照旧不安详的_snprintf
查阅库函数手册,找到这么一个函数_snprintf,其函数原型如下:
int _snprintf( char *buffer, size_t count, const char *format [, argument] …… );
这个函数答允界定方针地点尺寸,可是,由于研究拷贝函数的履历,我猜疑它也有strncpy沟通的问题,因此,我写了这么一段代码测试:
void sprintfTest1()
{
int i;
char szBuf[128];
for(i=0;i<128;i++) szBuf[i]='*';
szBuf[127]='\0';
char szBuf2[256];
for(i=0;i<256;i++) szBuf2[i]='#';
szBuf2[255]='\0';
_snprintf(szBuf,8,szBuf2);
printf("%s\n",szBuf);
}
公然,措施输出如下:
########***********************************************************************************************************************
同样的错误,没有用“\0”竣事,我必需别的想要领。
别的,还发明白别的一个不敷,就是这个时候,_snprintf函数返回-1,不再返回打印的字符数,那么,我们假如利用如下代码将会造成逻辑错误,甚至大概瓦解:
char szBuf[256];
int nCount=0;
while(1) //这里暗示轮回结构
{
nCount+=_snprintf(szBuf+nCount,256-nCount,”... ...”); //多个字符串结构成一个字符串
}
留意,代码操作_snprintf返回的值,来确定下一个起始点,这很常用,可是,当_snprintf返回-1的时候,有大概会写到*(szBuf-1)的位置上,典范的内存写出界。
2.3 安详地字符串结构函数
颠末仔细思考,我结构了如下一个函数:
int xg_printf(char* szBuf,int nDestSize,char *szFormat, ...)
{
int nListCount=0;
va_list pArgList;
va_start (pArgList,szFormat);
nListCount+=_vsnprintf(szBuf+nListCount,
nDestSize-nListCount,szFormat,pArgList);
va_end(pArgList);
*(szBuf+nDestSize-1)='\0';
return strlen(szBuf);
}
留意,这里我回收了变参函数设计,为的是和sprintf一样利便,别的,最后一个return也很是重要,因为许多场所,我们需要知道毕竟打印了几多字符。将这段函数代入上面的例子后一切正常。
总结:C语言字符串库函数大概是出于提高机能目标,在一旦条件不足的时候,往往直接返回,忘了回收“\0”竣事字符串。这会造成下一次读取字符串时,数据界线不行控。名目化打印函数,返回值设计不公道,不永远是一个正整数,会造成逻辑隐患。因此,发起各人有乐趣可以参考一下我提供的两个函数。
别的,以上仅为我小我私家测试之作,限于本人程度所限,必定尚有没思量到的处所,接待各人展开接头。假如各人需要上面的源代码,请和我接洽。