C语言中strcpy、strncpy、memset函数的使用

   

        在C语言中会经常用到拷贝函数,本文记录下strcpy、strncpy、memset函数的使用。


     strcpy函数与strncpy函数

     
        函数作用:将一个字符串拷贝到另外一个字符串。把src所指向的以null为结束的字符串拷贝到dest所指向的内存空间。dest是char*类型的,而src是const char*类型的。说明src指向的内存空间在函数中只能读而不能修改。函数返回值是char*类型的,返回的是修改后那个字符串的地址。

        注意:strcpy函数会拷贝src所指向的字符串,包括结束符('\0')到dest所指向的区域。因此保证了dest中是以'\0'结尾的字符串。strcpy只知道src字符串的首地址,它会一直拷贝到'\0'位置。dest所指向的区域必须足够大来完成拷贝。
                  strncpy功能是类似的,只不过是拷贝n个byte到dest区域。如果src的长度小于n,则会用空字符来填充dest区域。


       strcpy使用注意两点:1) src字符串以'\0'结尾    2)dest区域足够大能够把dest全复制进来 ,否则会造成缓冲区溢出。
       Error Example:
//
//  main.cpp
//  strcpyExample
//
//  Created by mini on 12/24/13.
//  Copyright (c) 2013 mini. All rights reserved.
//

#include <iostream>
#include <string.h>

using namespace std;

int main(int argc, const char * argv[])
{
    char buf1[10];
    char buf2[10];
    //char *s = "hello world";
    strcpy( buf1, "hello world" );
    strcpy( buf2, "holly shit!");
    cout << &buf1 << endl;
    cout << &buf2 << endl;
    cout << buf1 << endl;
    cout << buf2 << endl;
    
}

        OutPut:
0x7fff5fbffa5e
0x7fff5fbffa54
!
holly shit!
Program ended with exit code: 0
        分析:函数堆栈是向下生长的。从内存高地址 -> 低地址生长。也就是栈顶的地址要比栈底低。所以buf1的地址高(栈底),buf2的地址低(栈顶),分配的两个字符数组都是10,要复制的内容都会越界。复制完两个字符串的内容如下图所示:





        So,在使用strcpy的时候要注意越界的问题。函数的实现者无法得知src字符串的长度和dest内存空间的大小。所以”确保不会写越界“应该是函数调用者的责任。调用者提供的dest参数应该指向足够大的内存空间,“确保不会读越界”也是调用者的责任,调用者提供的src参数指向的内存必须应该确保'\0'结尾。

        写越界可能当时不会出错的,而在函数返回的时候会出现错误,原因是写越界覆盖了保存在栈帧上的返回地址,函数返回的时候跳转到非法地址,因而出错。这称作段错误。如果仅仅是段错误还不算严重,更严重的使缓冲区溢出bug经常被恶意用户利用,是的函数返回跳转到一个预先设计好的地址,然后执行实现设计好的指令,如果设计巧妙可以启动一个shell,然后随心所欲的执行命令。如果一个拥有root权限的执行程序如果存在这样的bug,后果是相当严重的。这种技术也是当前一种非常流行的cracker technique。

       另外:src和dest指向的内存空间是不能存在重叠的。一般具有指针参数的C标准库函数基本上都会有这个要求,每个指针参数所指向的内存空间不能相互重叠。


        strncpy的参数n指定最多从src中拷贝n个字节到dest中,也就是说,如果拷贝到'\0'就结束。如果拷贝到n个字节还没有碰到'\0',那么同样结束。调用者来负责提供适当的n 值,比如让n的值等于dest所指向的内存空间大小,例如下面这样:
    char buf1[10] = "abcdefghi";
    strncpy( buf1, "hello world", sizeof( buf1 ) );

             这段代码可以将buf1的空间全部填满,但是这样无法保证dest是'\0'为结尾的。so,读的时候会出现读越界的错误,调用者要确保dest以'\0'结尾。你可以手动加上这么一句话
    char buf1[10] = "abcdefghi";
    strncpy( buf1, "hello world", sizeof( buf1 ) );
    buf1[ sizeof(buf1) - 1 ] = '\0';

            strncpy还有一个特性,如果src字符串全部拷贝完了之后还不如n个字节,那么还差多少个字节就会补全都少个'\0'。

       strcpy和strncpy的返回值:
       这两个函数都返回dest指针,dest本来就是作为调用者传进去的,那么为什么还要返回来呢。这样做是为了把函数调用可以当做一个指针类型的表达式使用。例如printf("%s\n",strcpy(buf,"hello")),如果strcpy范回void的话你是不可以这么直接使用的。

      strcpy和strncpy总结:
      使用strcpy的时候最好确保不要写越界和读越界,strncpy的时候可以手动添加结束符号。例如:
strncpy(buf,str,n);
if( n > 0)
     buf[n - 1] = '\0';



     memset函数的使用:

       函数原型:
void * memset ( void * ptr, int value, size_t num );

            函数作用:
       
       memset函数把ptr所指向的内存地址开始的num个字节都填充为value的值。通常用于内存区清0.例如你定义一个int数组 int a[10],如果是a是全局数组或者静态变量则自动生成为0,如果是局部变量,则可以使用memset(a,0,10)清0,适用于初始化或者循环函数中的更新。
   
       Example:
    int a[10];
    for( int s: a )
        cout << s << " ";
    memset( a, 0, sizeof( a ) );
    cout << endl;
    for( int s: a )
        cout << s << " ";
    cout << endl;
            OutPut:
0 1 1606417120 32767 0 1 0 0 1606417136 32767 
0 0 0 0 0 0 0 0 0 0 

         memset可以方便的清空一个结构类型的变量或数组。

       Example:
       如果我们定义了一个结构体数组:
struct student {
    char name[20];
    int age;
    int number;
};

           一般情况下,有一个结构体变量student s,如何清空s的各个值?可以用下面的方法:
    s.name[0] = '\0';
    s.age = 0;
    s.number = 0;

           这种方式实在是效率太低下了,代码量也大。我们可以用memset来执行:
    struct student s ;
    strcpy( s.name, "hehe" );
    s.age = 10;
    s.number = 10;
    cout << s.name << " " << s.age << " " << s.number << endl;
    memset( &s, 0, sizeof( s ) );
    cout << s.name << " " << s.age << " " << s.number << endl;

        OutPut:
hehe 10 10
 0 0

        如果是结构体数组,则可以这样使用:
    student s[10];
    memset( s, 0, sizeof( student ) * 10 );

        

        memcpy和memmove函数的使用:


       函数原型:
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);

       函数作用:
       
       memcpy函数从src所指的内存地址拷贝n个字节到dest所指的内存地址。
       memmove也是从src所指向的内存地址拷贝n个字节到dest所指的内存地址。虽然叫move 但是其实也是拷贝,它和memcpy有一点不同,memcpy的两个参数src和dest所指向的内存区间如果重叠的话,无法保证正确的拷贝,而memmove却可以正确拷贝。如果定义一个数组char data[20] ="hello world\n",如果想把其中的字符串往后移动一个字节变成"hhello world\n",则可以使用memmove(buf+1,buf,13)则可以实现该功能,而memcpy(buf+1,buf,13)则是无法完成拷贝的:

       这两个函数与strcpy和strncpy的区别:

       strcpy的那两个函数是遇到'\0'就结束。memcpy并不是遇到'\0'就结束。而是一定会拷贝完n个字节。拷贝函数的命名规则就是:以str开头的函数出来以'\0'结尾的字符串,而已mem开头的函数不关心'\0'字符串。mem开头的函数并没有把参数当做字符串来对待,因此参数的指针类型是void*而不是char*

       Error Example:
int main(int argc, const char * argv[])
{
    char data[20] = "Dont give up!";
    memcpy( data + 1, data, 13 );
    char data2[20] = "Dont give up!";
    memmove( data2 + 1, data2, 13 );
    cout << data << endl;
    cout << data2 << endl;
}

        OutPut:
DDont givv upp
DDont give up!

       
第一个函数使用的会出现乱码现象,而第二个会正常执行。这就是两者的区别。












































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值