1.C++中,内存分为五个区域,堆,栈,自由存储区,全局/静态存储区,常量存储区;
栈,函数调用时,函数内部局部变量的存储单元都可以在栈上创建,函数调用结束时,存储单元被自动释放;由编译器负责;
堆,使用new分配的存储单元,由程序员自己负责delete释放;如果忘记了回收,当程序运行结束后由系统回收这部分内存;
自由存储区,由malloc分配的存储单元,使用free释放;
全局/静态存储区,它们共占一部分内存;
常量存储区,存放常量,内容不允许修改;
2.嵌入式环境中,内存经常成为瓶颈;为了防止程序员不定时的new不定大小的空间,有必要重载new/delete操作符,实现人为的高效的内存管理;
可以重载全局的new/delete操作符;
也可以重载某一个类的new/delete操作符;
如果使用对象数组的话,需要重载new[]/delete[]操作符;
3.几种情况;
1.如果程序中对象的调用关系非常复杂,比如在某个地方做了一个new操作,而在很远的地方也会使用到这个对象,之后很容易忘记该对象是否被释放。这种情况最好重新设计数据结构,从根本上改变;
2.return语句,写错,不要返回指向栈内存的指针以及引用,因为它们会在函数执行结束之后被自动释放;(下面还会详细讨论)
3.使用指针的习惯要好,用前先申请分配,只有判断是否申请成功,成功了则赋初值,使用完了释放,释放后再赋空值;
4.指针与数组的比较
1.数组一般只存在于静态存储区(全局数组)、或者栈中;数组名对应着一块内存,而不是指向一块内存,数组名的地址与容量在生命周期内不变;只有数组的内容可以改变;
2.指针可以随时指向不同类型的内存块;
3.修改内容比较:
char a[]="hello";
char *p="hello";
a[2]='a'; //正确
p[1]='a' //错误,因为p指向的常量字符串,内容不可以被修改;
4.内容复制与比较
指针与数组都不可以直接复制,比如两个数组,a,b,a=b是错误的;复制需要通过strcpy(),比较需要使用strcmp(),这点数组与指针一样;
5.计算大小
sizeof可以计算数组的大小,但是无法计算指针指向地址的大小,C/C++还没有提供这种机制,只能在申请时知道大小;当数组作为函数的参数时,大小自动退化为同类型的指针的大小;
int function(char a[100])
{
cout<<sizeof(a)<<endl;//返回的是4,而非100;(32位系统)
}
5.变量的生存周期
不要返回指向栈内存的指针或者引用,因为它们会在函数执行结束之后被自动释放;
例1:
#include <stdio.h>
char *returnStr()
{
char *p="hello world!";
return p;
}
int main()
{
char *str;
str=returnStr();
printf("%s\n", str);
return 0;
}
这样是没问题的,因为p指向的是内存中的常量存储区,函数返回的是指向常量存储区的指针;当函数执行结束后,字符串所在的常量字符串的内容不会被回收,因此能够访问;
例2:
#include <stdio.h>
char *returnStr()
{
char p[]="hello world!";
return p;
}
int main()
{
char *str;
str=returnStr();
printf("%s\n", str);
return 0;
}
执行错误;函数returnStr中,p是一个数组,而数组一般只存在于全局存储区或者栈中,很明显,函数中的数组属于局部变量,不可能存在于全局存储区中,只能是在内存中的栈中分配的空间;而当函数执行结束后,栈内存是要被回收的,这时候返回的是一个已经被释放了的地址;打印出来的可能是乱码;
例3:
int* func( void )
{
static int a[10];//如果去掉,static,失败
........
return a;
}
同上,函数中的数组是在栈上分配的,如果不加static关键字的话,当函数执行结束后,栈空间会被释放,返回的是一个指向被释放了的地址空间;如果加上static关键字的话,数组在静态存储区分配,在程序执行结束后静态存储区的空间才会被释放,因此以上代码是可行的。
例4:
void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) *num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}
上面这段代码,函数中p指向的内存空间是在堆上,当函数调用执行结束后申请的空间并没有被释放(实际上,每次执行这个函数都会造成内存泄露,大小就是malloc出来的内存块的大小);出现错误的原因是,在函数内部修改的形参,并没有真正的改变传入的形参的值;
毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。
实际上,
p=str;
p=(char *)malloc(sizeof(char) * num);
//str并没有改变
如果实在喜欢这种内存分配方式,可以传入二重指针,
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意参数是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
或者:
char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
例1,例2,例3主要涉及的都是变量的生存周期以及存储方式,例4是由于在函数内部修改的形参,并不能真正的修改传入的形参。