strcpy函数_跟我学嵌入式C库函数实现

1. 什么是C库函数

C语言的库函数并不是C语言本身的一部分,它是由 编译程序 根据一般用户的需要,编制并提供用户使用的一组程序。C的库函数极大地方便了用户,同时也补充了C语言本身的不足。在编写C语言程 序时,使用库函数,既可以提高程序的运行效率,又可以提高编程的质量。所有的C编译器都会实现C库函数,其中著名的C库有GNU发布的glibc。

2.为什么要造轮子写C库函数

在C语言开发中,我们直接包含相关库函数头文件(如string.h)即可使用字符串处理相关C库函数。但C语言不像其他面向对象语言,在安全处理等做了相关优化处理,如调用字符串C库函数就有很多限制,但C语言基于效率考虑,将这些留给调用者处理。如果没有考虑这些,写的C代码就很容易存在bug。

从零实现部分C库函数主要有要两点好处:
  • 熟悉常用的C库函数使用

  • 增强自己C基本功

3. 开始造轮子

下面是一些常见的字符串处理库函数,对于字符串处理函数处理的都为字符串,所谓字符串即为以‘\0’结尾的ASCII字符串。我们将以实现如下函数抛转引玉:

序号函数原型
1

size_t strlen( char *str )

函数返回字符串str 的长度

2

char *strcpy( char *to,const char *from )

复制字符串from 中的字符到字符串to,包括空值结束符

3
char *strncpy( char *to, const char *from, size_t count )

将字符串from 中至多count个字符复制到字符串to中。如果字符

from 的长度小于count,其余部分用'\0'填补

1)my_strlen()

//my_strlen()库函数实现size_t my_strlen(const char* str) {    size_t len = 0;        while (*str++ != '\0')     {        len++;    }    return len;}//my_strlen()库函数测试用例bool test1(const char *msg) {    if (strlen(msg) == my_strlen(msg))    {        std::cout << "单元测试1:测试通过!," << "消息长度:" << my_strlen(msg) << "。" << std::endl;        return true;    }    else    {        std::cout << "单元测试1:测试失败!" << std::endl;        return false;    }}

2)my_strcpy()

从my_strcpy()库函数实现我们可以发现,调用strcpy()是需注意两个问题:

  • 1.确保from指向的源字符串以'\0'为结尾的字符串,

  • 2. 确保to指向的空间足够大,至少比源字符串长度大,不然就会出现内存溢出问题

  //my_strcpy()库函数实现char* my_strcpy(char* to, const char* from){  char* pDes = to;  //从这我们可以看出,字符拷贝是以源字符结尾做为判断的,  //因此该库函数调用时应注意两点:   // 1.确保from指向的时以'\0'为结尾的字符串,  // 2. 确保to指向的空间足够大,不然就会出现内存溢出问题  while (*from != '\0')    {    *pDes++ = *from++;  }  *pDes = '\0';  //记得一定要补字符串尾,  return to;}//库函数测试用例//拷贝功能测试bool my_strcpy_test1(const char* from){  bool ret = false;  size_t msg_size = my_strlen(from) + 1;  char* pBuf1 = (char *)malloc(strlen(from) + 1);    if (pBuf1 == NULL )  {    return false;  }  memset(pBuf1, 0, msg_size);    my_strcpy(pBuf1, from);  if (!strcmp(pBuf1, from))  {    std::cout << "单元测试1:测试通过!," << "消息内容:" << pBuf1 << ":"<< std::endl;    ret =  true;  }  else  {    std::cout << "单元测试1:测试失败!" << std::endl;    ret =  false;  }  free(pBuf1);  return ret;}

3)my_strncpy()

从strcpy()的实现我们知道,这个函数存在一定安全风险,如果有恶意使用这个函数往我们的内存中拷贝一个很大的数据内容,而我们未做限制,就会导致我们的内存溢出,从而使得恶意攻击者可以修改我们的内存内容。 这就引出另外一个字符拷贝函数strncpy(),这个函数限制了最大的拷贝字节数。
//my_strncpy代码char* my_strncpy(char* to, const char* from, size_t count){  size_t cnt = 0;   size_t index = 0;  char* pDes = to;  while (*from != '\0' && cnt < count)    //从这可以看出一个问题,如果count小于源字符串长度,我们拷贝的字符串就不是真正的字符串。  {    *pDes++ = *from++;    cnt++;  }  for (index = cnt; index < count; index++)  //源字符串不够count长度, 剩余部分补‘\0'  {    *pDes++ = '\0';        }  return to;}
1. 测试单元1:拷贝长度小于源字符串长度
//单元测试1:拷贝长度小于源字符串长度bool my_strncpy_test1(void){  bool ret = false;  const char* msg = "hello,strncpy()";  char pBuf[100];    memset(pBuf, 0xff, 100);  my_strncpy(pBuf, msg, 7);  std::cout << "strncpy()拷贝内容:" << pBuf <<  std::endl;  if (!memcmp(msg, pBuf, 7))  {    std::cout << "单元测试1(拷贝长度小于源字符串长度):测试通过!,"  << ":" << std::endl;    ret = true;  }  else  {    std::cout << "单元测试1(拷贝长度小于源字符串长度):测试失败!," << std::endl;    ret = false;  }    return ret;}
测试结果: 拷贝长度小于源字符串长度我们看到测试单元测试通过,但我们打印出的拷贝内容出现了乱码,这是因为源字符串比要拷贝的字节数多,因此没有拷贝'\0'结束符,所以打印了多余的内容(见内存内容)

6f779816586ca6c8206ee8b8cc4ac378.png

438afb4a8fccf05c3dd76927b07e9e3e.png

1. 测试单元2:拷贝长度大于源字符串长度

//源字符串长度小于于countbool my_strncpy_test2(void){  bool ret = false;  const char* msg = "hello";  char pBuf[100];  //memset(pBuf, 0xff, 100);  memset(pBuf, 0xff, 100);  //my_strncpy(pBuf, msg, 7);  my_strncpy(pBuf, msg, 7);  std::cout << "strncpy()拷贝内容:" << pBuf << std::endl;  if (!memcmp(msg, pBuf, 7))  {    std::cout << "单元测试2(拷贝长度小于源字符串长度):测试通过!," << ":" << std::endl;    ret = true;  }  else  {    std::cout << "单元测试2(拷贝长度小于源字符串长度):测试失败!," << std::endl;    ret = false;  }  return ret;}
内存图

d6db92019867135a7c105b36835734e2.png

测试结果:拷贝长度小于源字符串长度

我们看到测试单元测试通过,打印也正常 (见内存内容) 4) 安全函数

如果你是用visual studio进行编码测试,使用以上函数编译器会告警,并提示你有其他的安全函数可调用(如,strcpy_s, strncpy_s)。

以我们实现的strcpy()来考虑,这个函数还有什么问题呢?

  • 对输出的参数指针未做NULL判断处理。

  • 目的内存可能不足,造成溢出

4. 造轮子总结

  从以上我们的代码实现,我们可以看到每个函数使用要注意的事项,以及相关替代函数又避免了那些东西,这些对提高我们的C基础是十分有效的。

C库函数包含巨大的函数功能,我们无需全部实现,也不可能全部实现。

我们写C库造轮子的目的是熟悉常用的C库函数并且夯实C语言基础,以下是本人建议需要造轮子的函数。
  • 字符串相关库函数

    strcpy(), strncpy(), strcat(),strncat(),isdigit(),strstr()等

  • 内存处理相关函数

        memcpy(),memcmp()等。C库造轮子时应遵循如下流程: 

4fa6084e8e8b84667fbf76d47d934043.png

5. C语言流行趋势

a458b338a86a4c3b8608c659e1a8c94b.png

6. 总结 

对于嵌入式软件开发而言,C语言是目前唯一的选择。但C语言的应用远超与此,C实现了linux操作系统,C实现强大的编译器gcc等,目前C仍然是最流行的编程语言之一。对应希望从事嵌入式软件开发的人,自己造轮子实现C库函数,对应夯实C语言基础,学好C语言这一强大武器十分必要。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值