这里讨论C语言标准库中各类常用函数,以及它们的高危情况。
1、atoi 函数
这个函数是转换输入字符串转换为整型数。
对于该函数的实现需要考虑以下几个方面:
- 输入字符串为NULL;
- 输入的字符包含前导的空格;
- 输入开始是否包含符号‘+’、‘-’;
- 输入的字符是否合法(对于十进制‘0’~‘9’为合法的输入);
- 计算出的数值为 long int,足够判断溢出;
- 数据溢出的处理(上溢出时,返回最大正数;下溢出时,返回最大负数);
上面的实现比较棘手的就是数据溢出的处理:这里我们用计算出的数值与最大值(最小值的无符号型)/10 进行比较,小于自然不会溢出,由于负数的最大值是-2147483648,最大值是2147483647,个位数不是9,所以还需考虑等于的情况下,个位数的比较。
将计算出的数值与最大值(最小值的无符号型)/10 比较而不是计算出数值10 与最大值比较,是因为计算出的数值10 有可能本身就溢出了。比如输入字符串为”314748364“,计算出的数值为314748364,然后其*10,必然会溢出出错,所以只能进行最大值 /10 操作。代码如下:
1 |
|
\2、strcpy 函数和 memcpy 函数**
strcpy 函数可以复制以null 为退出字符的存储器区块到另一个存储器区块内,只用于字符串的复制,字符串在存储器内以连续的字节区块组成,strcpy 可以有效复制两个配置在存储器以指针回传的字符串(也就是字符指针或字符串指针)。
函数原型如下:
1 |
|
先下面看看微软的写法:
1 | char * __cdecl strcpy(char * dst, const char * src) |
上面这个写法为了提高性能,减去了那些安全检查,其余漏洞后面讨论。
除去安全性检查,strcpy 还不允许 src 与 dst 两内存块有重叠。只要有重叠势必会写入修改src 只读区域,这是不允许的,另外有重叠区域,当dst 在高地址时,复制过来的可能就是dst 前面部分的字符了。鉴于上面分析,我们写出下面实现代码:
1 | char * strcpy(char * dst, const char * src) |
上面的程序最后返回 char* 类型,是为了使函数能够支持链式表达式,增加了函数的“附加值”。
实际上上面对于地址重叠还有一个更好的解决方法,那就是判断地址是哪部分重叠,如果dst 地址位于 src 前面,按照正常的赋值操作是没问题的,如果dst地址位于src后面,那么则从src尾部开始复制,这样可以解决地址重叠问题。代码就不贴出来了,可自行画一个示意图,一目了然。
另外值得注意的是:上面那个函数一样,这是strcpy 的硬伤,就是必须为目标字串分配足够的空间,如果目标字串的长度小于源字串的长度,那么在复制操作的时候会出现缓存溢出。在拷贝字符串的时候没有越界检查,这使得 strcpy 成为一个高危函数。
从strcpy 函数的参数就可以看出,strcpy 只能复制字符串,也不需要指定复制长度(strncpy 需要指定长度)
下面顺带看看memcpy 函数
1 | void * memcpy(void * dst, const void * src, size_t count) |
memcpy 接受void* 类型的形参,这使得memcpy 函数可以复制任意内容。strcpy 拷贝是遇到‘\0’ 就停止,而memcpy 并不是遇到‘\0’ 就结束,而是一定拷贝 count 个字符。一般而言,在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
3、strcat 函数
函数原型:
1 | char * strcat(char * dst, const char * src) |
功能是把 src 所指字符串添加到 dst 结尾处(覆盖dst 结尾处的’\0’)并添加’\0’,最终返回指向 dst 的指针。
1 | char * strcat(char * dst, const char * src) |
src 和 dst 所指的内存区域不可以重叠且 dst 必须保证有足够的空间来容纳 src 的字符串,否则会出错。C 语言标准库中strcat 函数同 strcpy 函数一样,没有保证dst 有足够的空间容纳操作后的字符串,也使得strcat 成为一个高危函数。
4、strcmp 函数
该函数用于比较两个字符串
1 | int strcmp(const char * src, const char * dst) |