关于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常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。