1->内存分配方式:
内存分配方式有三种:
a):从静态存储区分配。例如程序中定义的全局变量和static变量就是这种方式分配,内存在编译的时候已经分配好了,这块内存在程序的整个运行期间都存在。
b):在栈上创建。这种情况程序的是最多的,例如我们再程序中定义的int var就是这种情况的内存分配方式。在执行函数的过程中,函数内的局部变量的存储单元就是在栈上创建的,在函数执行结束时,这些存储单元自动被释放。
c):从堆上分配,亦称为动态内存分配。程序在运行的时候会用到malloc或new申请任意多少的内存,程序员自己负责在何时用free或者delete释放内存。动态内存的生存期由我们自己决定,在使用上相对比较灵活。
2->常见的内存分配错误和处理方法:
a):内存分配未成功,却使用了它(在使用内存之前检查其是否为空)。如果指针p是函数的参数,那么在函数的入口处使用assert(p!=NULL)进行检查。如果采用malloc分配的内存,加上判断语句if(p==NULL)或者if(p!=NULL)进行防错处理
b):内存分配虽然成功,但是尚未初始化就引用它。例如:未初始化的数组误以为它里面存放的数据都为0
c):操作越界
d):忘记释放内存
e):释放了内存却继续使用它。一般有三种情况:程序的调用过程过于复杂,很难理清哪个动态内存已经释放。函数的return语句返回局部变量的指针或者引用。使用了free或者delete释放内存之后,没有将指针置为NULL。导致产生了野指针。
3->指针和数组
数组要么在静态内存区被创建(例如全局数组),要么在栈上被创建,数组名对应(而不是指向)一块内存,其地址在生命周期内保持不变,可以改变的是数组内的内容。
指针可以随时指向任意类型的内存块,它的特征就是“可变”,我们常常用指针来操作内存。
a):关于二者的内容修改
Code:
char a[]="hello";
a[0]='X'; cout<<a<<endl;
char *p="World"; //P指向常量字符串 p[0]='X';//试图修改常量字符串的内容,但是编译器发现不了这样的错误
cout<<p<<endl;
解释:字符数组a的容量是6个字符,其内容为“hello\0”(在栈上),a的内容是可以改变的。而指针指向的是常量字符串“world”(位于静态存储区)内容是“world\0”,常量字符串的内容是不可以被修改的。p[0]='X',试图修改常量字符串的内容而导致运行错误。
b):关于两者进行内容复制
不能对数组名进行直接复制与比较,若想把数组a的内容复制给数组b,不能用语句b=a,应该采用标准库函数strcpy进行。同样要比较数组a和数组b中的内容是否相同,不可以直接用if(a==b)进行判断,要用库函数strcmp进行比较。
语句p=a并不是把a的内容复制给p,而是将a的地址赋值给p。要想复制a的内容,可以先用malloc函数为p分配一个strlen(a)+1字符串的内存,再调用strcpy函数进行字符串的复制。同样指针的比较不是内容的比较而是地址的比较。
c):关于两者内存容量的计算
用运算符sizeof可以计算出数组的容量(字节数)。例如a[]="hello world";sizeof(a)的值是12,(\0也要计算在内),而对于指向a的指针p,sizeof(p)的值却是4,这时候得到的是一个指针变量的字节数。相当于计算sizeof(char*),而不是p所指向的内存容量。
另外当数组作为函数的实参进行传递时,数组退化为指针。
4->指针传递内存
如果函数的参数是一个指针,不要指望该指针去申请动态内存。例如,Test函数的语句并没法获得Getmemory(str,200)动态分配的你才,str仍然指向NULL
void GetMemory(char *p,int num){
p=(char *)malloc(sizeof(char)*num)
}
void Test(){
char *str=NULL;
GetMemory(str,100); //str依然为空
strcpy(str,"hello"); //运行错误
}
解释:编译器总是要为每个参数制作临时副本,指针参数p的副本_p,编译器是_p=p.如果函数体内的程序修改了_p的内容将导致参数p的内容作了相应的修改。但是在本例中_p申请了新的内存,只是把_p所指的内存地址改变了,没有改变p所指的内存。所以p还是为NULL。
如果一定要用指针去申请内存,那么就采用指针的指针,将上述代码做如下的修改:
void GetMemory(char **p,int num){
*p=(char*)malloc(num*sieof(char));
}
void Test2(){
char *str=NULL;
GetMemory(&str,100);
strcpy(str,"hello");
cout<<str<<endl;
free(str);
}
同样我们也可以用return返回动态分配的内存:
char * GetMemory(int num){
char *p=(char*)malloc(sizeof(char)*num);
return p;
}
这时候要特别注意,不能返回栈内存的指针。
char *GetString(){
char p[]="hello word";
return p; //这个是完全错误的程序
}
5->关于free和delete
free和delete只是把指针所值的内存给释放掉,但是还保留指针本身。如果不把指针设置为NULL,就会成为野指针。
6->动态内存会随着程序结束自动释放吗?
答案是不会的。这个知识涉及到了两个理论:指针消亡了,并不代表它所指向的内存会被自动释放。内存释放了并不代表了指针会消亡或者为NULL、
7->野指针
”野指针“不是NULL指针,是指向”垃圾“内存的指针。
形成野指针的原因:指针变量没有初始化,任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值一般是随机的,所以在指针变量被创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
另外指针p被free或者delete之后,没有置空,同样也会使指针p成为野指针。还有一种就是指针越界。
8->malloc/free和new/delete
malloc与free是C++/C 语言的标准库函数,new/delete是C++的运算符,它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动调用对象的构造函数,对象消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器的控制范围之类,不能把构造函数和析构函数的任务强加于malloc/free.
因此C++语言需要一个能完成动态内存分配和初始化的工作的运算符new,以及能够完成清理与释放内存工作的运算符delete。
9->关于内存耗尽的问题
如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败,就是通常所说的内存耗尽问题。
一般的处理方法就是判断申请的地址是否为NULL。
10->再看看malloc/free
malloc的函数原型:void *malloc(size_t size);
申请一个长度为length的整数类型的内存:int *p=(int *)malloc(sizeof(int)*lenght);
注意:malloc返回值类型是void*,所以在调用malloc时要显示地进行类型转换,将void* 转化成所需要的内存类型
另外malloc函数本事不识别要申请的内存是什么类型,它只关心内存的总字节数。
free的函数原型:void free(void *memblock);