c语言中strncpy函数怎么用,C语言中strcpy、strncpy、memset函数的使用

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

strcpy函数与strncpy函数

e6081b6504c04f5825568767b19ae40a.png

函数作用:将一个字符串拷贝到另外一个字符串。把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

#include

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,要复制的内容都会越界。复制完两个字符串的内容如下图所示:

df2b6107a4c135d95b19a2b63f389b20.png

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、付费专栏及课程。

余额充值