面试3:memcpy与memmove比较与C语言实现、const(常量指针与指针常量)

关于memcpy

void *memcpy(void *dst, const void *src, size_t n)

作用:拷贝src所指向的内存地址中的内容前n个字节到dst所指向的内存地址上。

1.当复制的字节数n超出了dst的空间容量,或者n超出src的容量,是很危险的。需要检查是否有溢出的情况出现。

2.该函数并不会检查参数dst和src所指向的地址空间是否大小相同。

3.如果src地址小于dst地址(即地址重叠),就会出现dst无法存取完整数据,造成src数据丢失。

总结:前一个指针参数所指向的内容前n个字节复制到第二个指针所指向的地址。如果复制的字节数超过第二个指针所指向的地址空间大小,就会产生溢出,造成数据丢失,但是这个函数自己不会去检查比较两个指针所指向的地址空间大小,需要我们自己去检查是否存在溢出情况。

memcpy本身是有bug的,并没有解决覆盖问题,可以用memmove代替,也可以自己实现。

memcpy和memmove

它们都是C语言的库函数,除了可以拷贝字符串以外,还能拷贝其它类型的数组。

strcpy和strncpy只能拷贝字符串数组。

void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);

区别:在于关键字restrict, memcpy假定两块内存区域没有数据重叠,而memmove没有这个前提条件。
如果复制的两个区域存在重叠时使用memcpy,其结果是不可预知的,有可能成功也有可能失败的,所以如果使用了memcpy,程序员自身必须确保两块内存没有重叠部分。

总结:内存区域存在重叠的问题(当第一个指针所指向的地址大于第二个指针所指向的地址时,造成数据丢失)

正常情况
在这里插入图片描述

内存地址重叠
在这里插入图片描述

memcpy的实现

顺序循环,一个字节一个字节的拷贝即可。

//memcpy的实现

#include <stddef.h>        //使用size_t
void *memcpy(void *s1,const void *s2,size_t n)
{
   char *p=s1;   //定义一个指针并初始化,p=&s1,p=s1
   const char *q=s2;
   while(n--)
       *p++==*q++;
   return s1;
}

memmove的实现

memmove会j检查是否存在内存覆盖,如果发现会覆盖数据,简单的实现是调转开始拷贝的位置,从尾部开始拷贝。

//memmove的实现
#include <stddef.h>     //  size_t
void *memmove(void* s1,const void *s2,size_t n)
{
    unsigned char *p=s1;   //定义一个无符号字符型指针并初始化
    const unsigned char *q=s2;
    if(_np_anyptrlt(q,p))     //__np_anyptrlt是一个简单的宏
             for(p+=n,q+=n;n--)
                    *--p=*--q;
     else
              while(n--)
                     *p++=q++;
     return s1;
}

__np_anyptrlt是一个简单的宏,用于结合拷贝的长度检测s1与s2的位置,如果s1和s2指向同样的对象,且s1比s2地址小,就需要从尾部开始拷贝。否则就和memcpy处理相同。
但是实际在C99实现中,是将内容拷贝到临时空间,再拷贝到目标地址中

//memmove实现

#include <stddef.h>      //size_t
#include <stdlib.h>        //   memcpy

void *memmove(void* s1,const void s2,size_t n)
{
         unsigned char tmp[n];
         memcpy(tmp,s1,n);
         memcpy(s2,tmp,n);
         return s2;
.
}

memcpy的速度比memmove快一点,如果使用者可以确定内存不会重叠,则可以选用memcpy,否则memmove更安全一些。

另外一个提示是第三个参数是拷贝的长度,如果你是拷贝10个double类型的数值,要写成n=sizeof(double)*10,而不仅仅是10。

关于const(常量修饰符)

const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变,

https://blog.csdn.net/u014520797/article/details/81749964

https://blog.csdn.net/xingjiarong/article/details/47282255

https://blog.csdn.net/hansionz/article/details/80028161

1.const修饰*,表示常量指针,指向的地址可以改变,指向的地址中的内容不可以改变。

//const修饰*
int a=10;
int const * p=&a;   //定义一个整形指针并初始化 ,指向的内存地址只读
//int const * p =a;   这也是可以的
*p=100;// 这个是错误的,内容不可以改变,方向可以改变。
p=NULL;//合法的,指针变量可以修改

//const修饰指针变量,表示指针常量,指针本身就是一个常量,指向的地址不能被修改,指针指向的地址中的内容可以改变。
int a=10;
int * const p=&a;
*p=100;//合法
p=NULL;//这个是错误的

//const修饰指针变量,又修饰*,表示指向常量的常指针,指向不能改变,也不能改变指向的地址中的内容。
int a=10;
const int * p=&a;
*p=100;//这个是错误的
p=NULL;这个是错误的

常量指针与指针常量

常量指针:是指针,指向一个常量。指向的内容是常量。
不能通过常量指针改变变量的值,但是还是可以通过其他的引用来改变变量的值的。
常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址。

//常量指针,修饰局部变量
const int * a;
int const *a;

//通过其他引用改变变量的值
int a=10;
const int*b=&a;
a=15;

//常量指针可以改变指向的地址
int a=5,c=8;
const int*b=&a;
b=&c;

//修饰函数参数  防止修改指针指向的内容
void stringcopy(char *str,const char*temp);     //常量指针固定内容

指针常量,是一个常量。指针本身就是一个常量,不能指向其他地址。但是地址中存在的数值是可以改变的。

//指针常量
int a=5;
int *p=&a;
int *const c=&a;    //指针常量
*p=8;

//修饰函数参数,防止修改指针指向的地址

void swap(int *const p1,int *const p2);   //指针常量固定指向

区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。如果我们将星号读作‘指针’,将const读作‘常量’的话,内容正好符合。int const * n;是常量指针,int *const n;是指针常量。

指向常量的指针
既不能改变指向,也不能改变内容。

int a=10;
const int * p=&a;

//修饰函数的返回值
//函数返回值(即指针)的内容不能被修改,只能被赋给加const 修饰的同类型指针。
const char* getstring(void);     //指向常量的常指针,既固定内容,又固定指向。

const char* str=getstring();//正确用法
char *str=getstring();//错误用法

修饰全局变量

全局变量的作用域是整个文件,应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现。

如果一定要用全局变量,应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。

总结:使用const作为修饰符的好处:
1、预编译指令(#define)只是对值进行简单的替换,不能进行类型检查

2、可以保护被修饰的东西,防止意外修改,增强程序的健壮性

3、编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值