2.1 不安全
如上面的例子,我在修改copy函数的同时,还想到了另一个我常用的字符串构造函数。 显然,这个函数没有定义目标地址空间的大小,是不安全的。 以下代码将导致崩溃。 :
空白()
整数我;
字符 szBuf[128];
for(i=0;[127]='\0';
字符[256];
for(i=0;[255]='\0';
(szBuf,);
("%s\n",szBuf);
2.2 仍然不安全
查看库函数手册,找到这样的函数。 其函数原型如下:
int ( char *, count, const char * [, ] ... );
此功能允许您定义目标地址大小。 然而,由于我研究复制函数的经验,我怀疑它也有同样的问题。 因此,我写了这段代码来测试:
空白()
整数我;
字符 szBuf[128];
for(i=0;[127]='\0';
字符[256];
for(i=0;[255]='\0';
(szBuf,8,);
("%s\n",szBuf);
果然,程序输出如下:
########************************************************ ******************************************
同样的错误,不是以“\0”结尾,我必须找到另一种方法。
除此之外,还发现了另一个缺点。 此时函数返回-1,不再返回打印字符数。 那么,如果我们使用下面的代码,就会导致逻辑错误,甚至可能崩溃:
字符 szBuf[256];
整数=0;
while(1) //这代表循环构造
+=(szBuf+,256-,"... ..."); //多个字符串构造成一个字符串
请注意,代码使用返回值来确定下一个起点,这很常见。 但返回-1时,可能会写入到*(szBuf-1)位置,这是典型的内存写越界。
2.3 安全字符串构造函数
经过深思熟虑,我构造了以下函数:
int (char* szBuf,int ,char *, ...)
整数=0;
;
(,);
+=(szBuf+,
-,,);
();
*(szBuf+-1)='\0';
(szBuf);
请注意,我在这里使用了可变参数函数设计,以方便与将这个函数代入上面的示例后,一切正常。
总结:C语言字符串库函数可能是为了提高性能的目的。 当条件不够时,往往直接返回,忘记用“\0”结束字符串。 这会导致下次读取字符串时数据边界不可控。 格式化打印函数的返回值设计不合理,并不总是正整数,可能会造成逻辑危险。 所以我建议大家有兴趣的可以参考我提供的两个函数。
另外,以上只是我个人的测试工作。 限于我自己的水平。 一定有一些事情我没有考虑到。 欢迎大家讨论。 如果需要以上源码请联系我。
C语言并不是一种很方便的语言,它的字符串就是一个例子。 根据C语言的定义,“字符串是一段包含ASCII字符、以“\0”结尾的内存空间,总共可以存储n-1个字符。 根据这个描述,字符串处理确实很麻烦,而且很容易出错。
为了方便用户,C语言标准库为用户提供了一些字符串函数,如字符串复制、构造、清除等功能,在一定程度上方便了用户使用。 然而,我无意中发现这些功能仍然存在一些隐患。
事情很简单。 我注意到我编写的一些程序总是出现内存读写错误。 不过,仔细检查了我所有的数据以及相关的处理函数后,并没有发现任何错误。 于是我把怀疑的目光投向了我常用的一些字符串处理函数,比如等等,仔细跟踪了几下,发现内存错误就是从这里来的。 于是,我开始研究如何安全使用字符串这个话题。
1.字符串复制功能
1.1 不安全
首先,我写了一个这样的函数:
空白()
整数我;
字符 szBuf[128];
for(i=0;[127]='\0'; //构造一个全是*的字符串
字符[256];
for(i=0;[255]='\0'; //构造一个全是#的字符串
(szBuf,);
("%s\n",szBuf);
很简单,复制一个字符串到另一个空间,但是不幸的是,源字符串比目标地址长,所以程序就惨死了。
1.2 仍然不安全
通过上面的例子,我发现复制的时候需要额外输入一个参数来表示目的地址有多长。 查看C语言库函数说明,有一个可以实现这一目的。 该函数的原型如下:
char *( char *, const char *, count );
好了,现在我们的问题解决了,我写了如下代码:
空白()
整数我;
字符 szBuf[128];
for(i=0;[127]='\0';
字符[256];
for(i=0;[255]='\0';
(szBuf,,128);
("%s\n",szBuf);
看起来一切都很好,但是当我输出结果时,我发现了一个问题。 字符串后面有时会跟着几个奇怪的字符,好像不是以“\0”结尾,所以我把上面的复制语句改为“(szBuf,,8);”,只复制了8个字符,出现问题,程序输出如下:
########************************************************ **************************
果然,当请求的目标地址空间小于源字符串空间时,将不再使用“\0”来终止字符串。 隐患巨大。
1.3 安全字符串复制功能
我仔细想了想,我觉得我需要一个字符串复制函数,如下:
1. 允许使用整数来定义目标地址空间大小。
2. 当目标地址空间nD小于源字符串长度nS时,只需要复制nD个字节。
3. 无论如何,目标地址空间都应该以“\0”结尾,以保持合法的字符串标识。 因此,生成的字符串的最大长度为 nD-1。
所以,我写了一个这样的字符串复制函数:
无效(字符*pD,字符*pS,int)
(pD,pS,);
*(pD+-1)='\0';
这很容易。 将这个复制函数代入上面的例子,只输出7个“#”,结果是正确的。
1.4 关于内存读取错误的思考
我原本以为可以就此打住,但没过多久,我就发现了一个奇怪的现象。 该函数在VC的Debug模式下出现错误,但在该模式下一切正常。
我想了很久,终于有一天我实在忍不住了,决定解决这个问题。 我用自己的复制循环替换了上面的内容,并一步跟踪了它。 我想看看到底是怎么回事?
原因找到了。 我想复制一个256字节的字符串,但是复制到第33个字节时出现错误。 检查程序后发现我的源字符串空间只有32 Bytes。 原来上面的代码只是阻止了内存写越界,但是并没有对读越界进行检查。 在VC的Debug模式下,内存读取越界也是非法错误,所以报错。
知道了原因,解决办法就很简单了。 我将上面的复制函数更改为以下形状:
无效(字符*pD,字符*pS,int)
int nLen=(pS)+1;
if(nLen>) nLen=;
(pD,pS,nLen);
*(pD+nLen-1)='\0';
一切都好。