程序基础关于C语言字符串函数的思考

来源:互联网 发布:淘宝里面怎么办信用卡 编辑:程序博客网 时间:2024/04/30 10:14

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”来结束字符串。巨大的隐患。 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模式下,内存读出界也是一种非法错误,因此被报错。 知道了原因,解决就很简单了,我把上面的拷贝函数改成如下形状: 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”结束字符串。这会造成下一次读取字符串时,数据边界不可控。格式化打印函数,返回值设计不合理,不永远是一个正整数,会造成逻辑隐患。因此,建议大家有兴趣可以参考一下我提供的两个函数。 另外,以上仅为我个人测试之作,限于本人水平所限,肯定还有没考虑到的地方,欢迎大家展开讨论。如果大家需要上面的源代码,请和我联系。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 打老鼠被老鼠咬了了怎么办 制作棒棒糖时木棍翘起怎么办 兔子被打后怕我怎么办 兔子后腿摔断了怎么办 兔子腿骨头断了怎么办 减肥老是控制不住吃怎么办 不爱吃主食爱吃零食怎么办 猫咪奶涨的很硬怎么办 斩魂技能乱加的怎么办 狗狗不吃东西还拉稀怎么办 狗狗拉稀不吃饭怎么办 狗狗几天不吃东西怎么办 狗狗不吃不喝怎么办 拖鞋洗了有臭味怎么办 拖鞋湿水了很臭怎么办 棉被上渗了酸奶怎么办 酸奶弄在衣服上怎么办 孩子把褥子尿湿怎么办 小孩尿过的褥子怎么办 包包放着发霉了怎么办 被套盖久了发黄怎么办 小白鞋里面发霉怎么办 棉被淋了一点雨怎么办 苹果手机被拉进电话群怎么办 墙上的插座烧了怎么办 富士康早退4分钟怎么办 我老婆起泡疹腿剌痛睡不着了怎么办 oppo耳机孔坏了怎么办 魅族耳机口坏了怎么办 控制线的报验资料怎么办 人失踪报警派出所不管怎么办 铁板的货架久了怎么办 干镀锌让环保查了怎么办 水管软管生锈了拧不动怎么办 镀锌管会生锈吗.怎么办 冷镀锌钢管生锈了怎么办 卖了过期的东西怎么办 喝到了假的饮料怎么办 烧汤总是溢锅怎么办 脚踩垃圾桶坏了怎么办 连衣裙特别容易起褶怎么办