C各类库函数的实现(atoi,strcpy,strcmp...)

这里讨论C语言标准库中各类常用函数,以及它们的高危情况。

1、atoi 函数

这个函数是转换输入字符串转换为整型数。

对于该函数的实现需要考虑以下几个方面:

  1. 输入字符串为NULL;
  2. 输入的字符包含前导的空格;
  3. 输入开始是否包含符号‘+’、‘-’;
  4. 输入的字符是否合法(对于十进制‘0’~‘9’为合法的输入);
  5. 计算出的数值为 long int,足够判断溢出;
  6. 数据溢出的处理(上溢出时,返回最大正数;下溢出时,返回最大负数);

上面的实现比较棘手的就是数据溢出的处理:这里我们用计算出的数值与最大值(最小值的无符号型)/10 进行比较,小于自然不会溢出,由于负数的最大值是-2147483648,最大值是2147483647,个位数不是9,所以还需考虑等于的情况下,个位数的比较。

将计算出的数值与最大值(最小值的无符号型)/10 比较而不是计算出数值*10 与最大值比较,是因为计算出的数值*10 有可能本身就溢出了。比如输入字符串为”314748364“,计算出的数值为314748364,然后其*10,必然会溢出出错,所以只能进行最大值 /10 操作。代码如下:

#define MAX_UINT ((unsigned)~0)
#define MAX_INT  ((int)(MAX_UINT >> 1))
#define MIN_INT  ((int)~MAX_INT)

int atoi(const char *str)
{
	assert(str != NULL);

	long int res = 0;
	bool minus = false;
	int c;

	while (*str == ' ')//跳过开头空格
		++str;

	/*正负判断*/
	if (*str == '+' || *str == '-')
	{
		if (*str == '-')
			minus = true;
		++str;
	}

	/*只针对数字*/
	while (*str >= '0' && *str <= '9')
	{
		c = *str - '0';

		/*正数溢出判断,溢出则返回相应上限值*/
		if (!minus && (res > MAX_INT / 10 || (res == MAX_INT / 10 && c > MAX_INT % 10)))
		{
			res = MAX_INT;
			break;
		}
		/*负数溢出判断,这里的比较转换为无符号,大于则溢出*/
		else if (minus && (res > (unsigned)MIN_INT / 10
			|| (res == (unsigned)MIN_INT / 10 && c > (unsigned)MIN_INT % 10)))
		{
			res = MIN_INT;
			break;
		}

		res = res * 10 + c;
		++str;
	}

	return minus ? -res : res;
}

2、strcpy 函数和 memcpy 函数

strcpy 函数可以复制以null 为退出字符的存储器区块到另一个存储器区块内,只用于字符串的复制,字符串在存储器内以连续的字节区块组成,strcpy 可以有效复制两个配置在存储器以指针回传的字符串(也就是字符指针或字符串指针)。

函数原型如下:

#include <string.h>
char * strcpy(char * dst, const char * src);
/*把src的内容复制到dst,然后目的字符串dst指针*/

先下面看看微软的写法:

char * __cdecl strcpy(char * dst, const char * src)
{
	char * cp = dst;

	while (*cp++ = *src++)
		;               /* Copy src over dst */

	return(dst);
}

上面这个写法为了提高性能,减去了那些安全检查,其余漏洞后面讨论。

除去安全性检查,strcpy 还不允许 src 与 dst 两内存块有重叠。只要有重叠势必会写入修改src 只读区域,这是不允许的,另外有重叠区域,当dst 在高地址时,复制过来的可能就是dst 前面部分的字符了。鉴于上面分析,我们写出下面实现代码:

char * strcpy(char * dst, const char * src)
{
    char *ret = dst;
    int count = 0;
    assert((dst != NULL) && (src != NULL));//检查指针的有效性

    count = strlen(src);
    assert((src + count < dst) || (dst + count < src));//检查内存是否存在重叠区域,此处非最优方法
    
    while (*ret++ = *src++)
        ;
    return dst;
}

上面的程序最后返回 char* 类型,是为了使函数能够支持链式表达式,增加了函数的“附加值”。

实际上上面对于地址重叠还有一个更好的解决方法,那就是判断地址是哪部分重叠,如果dst 地址位于 src 前面,按照正常的赋值操作是没问题的,如果dst地址位于src后面,那么则从src尾部开始复制,这样可以解决地址重叠问题。代码就不贴出来了,可自行画一个示意图,一目了然。

另外值得注意的是:上面那个函数一样,这是strcpy 的硬伤,就是必须为目标字串分配足够的空间,如果目标字串的长度小于源字串的长度,那么在复制操作的时候会出现缓存溢出。在拷贝字符串的时候没有越界检查,这使得 strcpy 成为一个高危函数。

从strcpy 函数的参数就可以看出,strcpy 只能复制字符串,也不需要指定复制长度(strncpy 需要指定长度)

下面顺带看看memcpy 函数

void * memcpy(void * dst, const void * src, size_t count)
{
    void *ret = dst;
    assert((dst != NULL) && (src != NULL));
        
    while (count--)
    {
        *(char*)dst = *(char*)src; //强制转换为char*,因为char占一个字节
        dst = (char *)dst + 1;     //这样,地址增加一个字节位移,可以全部复制
        src = (char *)src + 1;
    }

    return ret;
}

memcpy 接受void* 类型的形参,这使得memcpy 函数可以复制任意内容。strcpy 拷贝是遇到‘\0’ 就停止,而memcpy 并不是遇到‘\0’ 就结束,而是一定拷贝 count 个字符。一般而言,在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。


3、strcat 函数

函数原型:

char * strcat(char * dst, const char * src)
功能是把 src 所指字符串添加到 dst 结尾处(覆盖dst 结尾处的'\0')并添加'\0',最终返回指向 dst 的指针。

char * strcat(char * dst, const char * src)
{
	assert(dst != NULL && src != NULL);

	int count = strlen(src);
	assert((src + count < dst) || (dst + count < src)); /*检查是否重叠*/

	char * cp = dst;
	while (*cp)                 /*先判断,再指针增加*/
		cp++;                   /* dst末位置 */

	while (*cp++ = *src++);       /* 拷贝 */

	return(dst);
}

src 和 dst 所指的内存区域不可以重叠且 dst 必须保证有足够的空间来容纳 src 的字符串,否则会出错。C 语言标准库中strcat 函数同 strcpy 函数一样,没有保证dst 有足够的空间容纳操作后的字符串,也使得strcat 成为一个高危函数。


4、strcmp 函数

该函数用于比较两个字符串

int strcmp(const char * src, const char * dst)
{
	assert((src != NULL) && (dst != NULL));

	int ret = 0;

	/*两个字符串自左向右逐个比较(ASCII值),直到出现不同字符或dst遇'\0'为止*/
	/*如果前面字符相同,dst的'\0'最后会参与比较*/
	while (!(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
		++src, ++dst;

	/*不同返回值对应不同比较结果*/
	if (ret < 0)
		ret = -1;
	else if (ret > 0)
		ret = 1;

	return(ret);
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值