舸's profile东楼夜话PhotosBlogLists Tools Help

东楼夜话

东风吹绽花千树,小楼一夜听春雨。

舸 肖

Photo 1 of 10
August 16

关于C语言字符串函数的思考

       C语言并不是一种很方便的语言,它的字符串就是一例。按照C语言的定义,“字符串就是一段内存空间,里面包含ASCII字符,并且,以”\0”结尾,总共能存放n-1个字符。”按照这个描述,字符串处理确实很麻烦,还很容易出错。

       为了方便用户,C语言标准库向用户提供了一些字符串函数,如字符串拷贝、构造、清空等函数,在一定程度上方便了用户的使用。但是,我无意中发现,这些函数还是有些隐患的。

       事情很简单,我注意到我写的一些程序,老是有内存读写错误,但是,经过仔细检查我所有的数据Buffer,以及相关的处理函数,又没有找到什么错误。于是我把怀疑的目光投向我常用的一些字符串处理函数上,如strcpysprintf等。在经过几次仔细地跟踪之后,我发现内存错误出自于此。于是,我开始研究如何安全地使用字符串这个话题。

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    内存读错误的思考

本来以为可以就此打住了,不过,没多久,我就发现一个奇怪的现象,这个函数在VCDebug模式下有错误,但是Release模式下却一切正常。

我奇怪了很久,终于有一天我忍不住了,决定解决这个问题,我把上面的memcpy用自己的一个复制循环代替,单步跟踪,想看看究竟怎么回事?

原因找到了,我希望拷贝一个256字节长的字符串,但是,拷贝到第33字节时出错,检查程序,发现我的源字符串空间只有32 Bytes,原来,我上面的代码只是防止了内存写出界,但没有针对读出界进行检查,在VCDebug模式下,内存读出界也是一种非法错误,因此被报错。

知道了原因,解决就很简单了,我把上面的拷贝函数改成如下形状:

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”结束字符串。这会造成下一次读取字符串时,数据边界不可控。格式化打印函数,返回值设计不合理,不永远是一个正整数,会造成逻辑隐患。因此,建议大家有兴趣可以参考一下我提供的两个函数。

另外,以上仅为我个人测试之作,限于本人水平所限,肯定还有没考虑到的地方,欢迎大家展开讨论。如果大家需要上面的源代码,请和我联系。

 

关于东楼

很多年以前的东东了,年少轻狂,翻出来做个纪念。
 
    东楼:姓肖名舸,西蜀人氏,年方四龄,出川远游,曾见东海日出、西岳落阳、北冥玄光、南极厚土、忽忽十八载......
    某夜,小楼独居,正是春暖花开时,却闻夜来风雨声,正是小楼一夜听春雨。待到玉兔西坠、日出东方,举头一望,却见紫气东来,忽有所悟,遂大笑三声,从此自号东楼......
 
 

   俺这人,有些矛盾,高中喜欢计算机,以至于连大学都没考好,只上了一个电大,学的还是工业与民用建筑专业。

    大学没毕业,就跑出去做生意,没想到,第一笔十八万的生意还让俺做成了,是卖给母校--华北石油一中的55台286电脑,自己提成一台286,这是平生自己挣的第一台电脑,过足了瘾。因为这,还到天津打了半年工,最辉煌的时候,和天津市副市长同桌喝酒,痛快!!!

    后来不行了,单位分工作啦,一下子当了三年施工员,主要给华北油田修了几条路,盖了几所楼房,其间艰辛,不提也罢。

    终于有一天,忍不住了,向俺娘提出要出去闯荡江湖,俗话说得好哇,儿行千里母担忧,俺就这样,在老母亲不舍的泪眼中,义无反顾的冲回老家--四川成都,一边心里笑着父母的多事,一边为自己的“勇敢”而自豪,现在想想,真是太天真了。对不起了,老爹老娘,因为那时俺还不懂事。

    回到成都后,有一家游戏公司正好在招兵买马,俺一头就闯了进去,居然被录取了,幸福!!!

    这一呆啊,就又是三年,所以说啊,俺这一辈子跟“三”是挂上勾了,大学三年,施工员三年,游戏程序员又三年,不过没白混,这不,出来啦!

    出来后,俺犯愁啊!怎么混啊?世界之大,没俺容身之处啊,难啊!!!

    没办法,就开了一间自己的公司,想过一把老板瘾。没想到哇没想到,就有人骗俺,想通过一些手腕把俺和俺的公司控制到手里捏,咱哥们什么人,就是把公司关了,也不能受这个窝囊气,于是俺就关了公司,重新打工。

    换了两家公司后,现在总算找了一个还不错的,俺也想开啦,这辈子大概就不是当老板的料,还是老老实实当俺的程序员吧!

    这不,都俩月了,俺觉得还可以,所以才建了这个网站,跟大伙聊聊大天儿。

   要说聊天儿,俺今年春节听了一首歌,好像叫什么回家看看,俺觉得特对味,出来几年了,山珍海味吃了不少,可俺就是忘不了俺娘给俺做的炒四季豆、黄瓜肉丁、炖带鱼。最近不知怎的,俺老是梦见哪个冬天的早晨,俺爹俺娘到车站送俺的事儿,隔着玻璃,俺看见他们老两口向俺挥手告别,又互相搀扶着,迎着初升的太阳慢慢的回家,一想到这儿,俺心里就不是个滋味儿,俺那不争气的眼睛,就又开始模糊了,呜呜,俺想俺娘...... 

迈普,想说爱你不容易

很多年前的东东了,翻出来看看这些发黄的照片
 

       迈普是什么?每次想到这个问题,思路中,总有那一丝薄雾漂过,朦朦胧胧,若隐若现。

       记得刚到成都那会儿,朋友三四,摆谈之中,迈普和托普一样,是一家很不错的IT公司,仅此而已。

       后来,每天早晚,上班下班,迈普是人民南路,锦江宾馆,河对岸那硕大的广告牌,一个巨大的MODEM,让我们感到Internet离我们已不再遥远。

       上了网,迈普是一个网站,公司的宗旨,礼贤下士的态度,让人心动。我尝试投递档案,却没有回音。

       打开报纸,迈普是一个个广告,礼貌、热情,字里行间,体现出一种自信。但是严格的要求,又让我感到如此遥远。

       进入激动网聊天室,迈普是吴总的话语,温和、彬彬有礼,却又有理有节。迈普是轻纱背后的朦胧,一会儿很近,一会儿又很远。

       要找工作了,Internet是河,我在河这边,迈普在那边,一根细细的电话线,连接希望,却又充满失望。

       好不容易,通过朋友,紧闭的大门,开了一条小缝。迈普,是航利九楼大厅的人来人往,是手机上跳动的时钟,是忐忑不安的心情,是投档成功的激动。

       正式面试,迈普是问题,是考验,是过后的一身冷汗。

       再见吴总,却没有了激动,依然的温和,依然的彬彬有礼,迈普,是温和后面的巨大压力。

       终于开始上班,迈普是上班第一天的忙乱,是面对任务的压力,也是海绵睡垫上的温馨。

       埋头工作,迈普是数字,是代码,是脑中复杂的程序逻辑。

       猛一抬头,迈普是忙碌,是急匆匆的人影,是分明很近,却又很远,同事那轻声的讨论。

       开会了,迈普是白板上的字,是投影仪的光,是激烈的争论,是争论后会心的微笑。

       下班了,走下大楼,回头望去,迈普是楼上那不灭的灯光,是灯光下勤奋的背影。

       回到家,迈普是家中敞开的大门,是家人期盼的笑容,是笑容中飘过的……一点寂寞。

       好不容易,软件写完了,却又马上被推翻,迈普,是善变的需求,是捉摸不定的客户。

       从头再来,继续努力,迈普,在代码背后微笑。

       终于又写完了,迈普是提心吊胆的等待,是测试人员严厉的评语,不断的失望,却又充满希望。

       软件测试通过了,迈普是成功之后的喜悦,是同事祝贺的目光,是说明书,是安装盘,是最后5%的严格要求,是疲惫的无奈。

       终于发工资了,迈普是柜员机上的数字,是税务单上的苦笑,是家人眼中的……一丝幸福。

       参加企业文化培训班,迈普掀开了面纱,她是家,是水,是诚信;是前辈的辛勤,是后辈的希望;是信任的竹竿,是脚下的叶子,是穿过的电网,是黑暗中的背后,无数坚强的手臂,织出的安全。

       座谈会上,迈普是罗总的笑容,灿烂而狡诘;是吴总的眼睛,智慧而温和;是蒋总的肩膀,瘦弱,但是坚强。

       牧马山庄,高效的习惯,简单实用的工具,IPD的严谨,改革的坚定,迈普是老师,睿智而又充满激情。

       回到公司,迈普是依然的忙碌,是同事灿烂的笑容,是冬日阳光下,慵懒的温和。

       联欢会上,迈普是激情,是欣慰,是热烈的舞姿,是得奖的喜悦,是关怀,是激励,是沉甸甸的任务书,是壮行酒杯中的一缕嫣红。

       春节回来,迈普是信任,是希望,是一个部门巨大的压力。

       新的开始,迈普是繁忙,是混乱,是惶恐不安,是忙碌的身影背后,那一双鼓励的眼睛。

       工作交接,迈普是版经期望的眼神,是任务表上冷酷的时间,是花总急切的心情。

       工作中,迈普,是弟兄们信任的目光,是他们勤奋的背影,是午夜灯火下通明的忙碌,是按时完成任务后,产品中心感谢的话语。

       新的任务不断地下来,迈普,是市场的呼声,是客户的期望,是呼声和期望背后,生存的压力。

       坚持努力地完成工作,迈普,是疲惫,是欣慰,是希望……

 

       今天,我在这里问自己,迈普是什么?

在我背后,是迈普十年的光辉,

在我前面,是更多的十年等待我们去创造。

 

迈普,是天边的一缕歌声:

“想说爱你,并不是很容易的事……

茶如人生

  今天又去高新区头上的山川茶楼喝茶,很喜欢那里的气氛。周末了,一个人休闲一下,来西安才两个月,不知不觉已去了好几次。
    喝茶一定要很中国才行,中国的窗棂,中国的藤椅,中国的雕梁画柱,中国的小桥流水,甚至身边的植物都要很中国的。
    也试过名典和上岛咖啡,喝的是地道铁观音和明前碧螺春,但不知道怎么的,总觉得不是那个味道。
    一个人到茶馆,点一杯信阳毛尖,很清的一种香味淡淡地划过鼻尖,直透肺腑。喝茶,有的时候是用鼻子喝的。
    待到茶熟,一小口一小口地喝,渐渐地,清新的感觉有了一些荡气回肠的味道,和洋鬼子的代码较了一个星期的劲,终于有了一个比较轻松的心情了。
    我一直比较喜欢一句广告词,竹叶青,平常心,喝茶,其实有时候要用心去喝的。
    请过几个朋友喝茶,喝之前,他们对于在茶馆一坐几个小时都觉得不可思议,喝过了,不说了。
    回去的路上,一个80后的小MM突然幽幽地说:“原来喝茶这个东东,会上瘾的。”
    我看着夜边的街灯发了一会怔,突然想起了成都,百花潭旁,琴台路口,那尊陆羽的雕像。灯影阑珊之间,这位千多年前的前辈,竟也露出了一丝微笑。
    喝茶还是喜欢绿茶,因为健康,更因为那种清香。当年4月上峨嵋,下金顶,过洗象池,山路一转,白茫茫的雪枝之下,蓦然一片绿色,有同行说,那是茶园,也不知是真是假。
    但突然对峨嵋毛峰有了好感,以致于以后一开始喝茶,就一直坚持喝这种略显夹口的绿茶。
    面对清茶,眼观鼻,鼻观心,心为之舒。袅袅清雾之中,整个人懒散地放松下来,邻座的轻声细语,也忽然遥远,世界一片安静。
    生活,其实有很多种活法。
    离开茶馆,整个人轻飘飘的,路过两个茶庄,店家小MM热情招呼。微笑着进去,细品一下新茶,随口点评几句,遇到喜欢的茶盒,买一个回来,心下窃喜。 
 
 
No list items have been added yet.