转自:http://blog.csdn.net/zztfj/article/details/7987645
一、内存分配
在谈述函数调用和返回值问题之前,先来看看C++中内存分配的问题。
C++编译器将计算机内存分为代码区和数据区。很显然,代码区就是存放程序代码,而数据区则是存放程序编译和执行过程出现的变量和常量。数据区又分为静态数据区、动态数据区,动态数据区包括堆区和栈区。
以下是各个区的作用:
(1)代码区:存放程序代码;
(2)数据区
a.静态数据区: 在编译器进行编译的时候就为该变量分配的内存,存放在这个区的数据在程序全部执行结束后系统自动释放,生命周期贯穿于整个程序执行过程。
b.动态数据区:包括堆区和栈区
堆区:这部分存储空间完全由程序员自己负责管理,它的分配和释放都由程序员自己负责。这个区是唯一一个可以由程序员自己决定变量生存期的区间。可以用malloc,new申请对内存,并通过free和delete释放空间。如果程序员自己在堆区申请了空间,又忘记将这片内存释放掉,就会造成内存泄露的问题,导致后面一直无法访问这片存储区域。
栈区:存放函数的形式参数和局部变量,由编译器分配和自动释放,函数执行完后,局部变量和形参占用的空间会自动被释放。效率比较高,但是分配的容量很有限。
注意:1)全局变量以及静态变量存放在静态数据区;
2)注意常量的存放区域,通常情况下,常量存放在程序区(程序区是只读的,因此任何修改常量的行为都是非法的),而不是数据区。有的系统,也将部分常量分配到静态数据区,比如字符串常量(有的系统也将其分配在程序区)。但是要记住一点,常量所在的内存空间都是受系统保护的,不能修改。对常量空间的修改将造成访问内存出错,一般系统都会提示。常量的生命周期一直到程序执行结束为止。
内存分配的举例:
int a=1; //a在栈区
char s[]="123"; //s在栈区,“123”在栈区,其值可以被修改
char *s="123"; //s在栈区,“123”在常量区,其值不能被修改
int *p=new int; //p在栈区,申请的空间在堆区(p指向的区域)
int *p=(int *)malloc(sizeof(int)); //p在栈区,p指向的空间在堆区
static int b=0; //b在静态区
二、函数调用
在弄懂内存分配的问题过后,来看看函数调用的过程:
执行某个函数时,如果有参数,则在栈上为形式参数分配空间(形参是引用类型的参数则除外;如果形参是对象则调用其“拷贝构造函数”给形参分配空间和赋值),继续进入到函数体内部,如果遇到变量,则按情况为变量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则直接返回调用该函数的地方(即执行远点),如果存在返回值,则先将返回值进行拷贝(如果是对象则调用其“拷贝构造函数”)传回,再返回执行远点,函数全部执行完毕后,进行退栈操作,将刚才函数内部在栈上申请的内存空间释放掉。
下面通过几个例子来谈谈内存分配和函数返回值的问题:
1、传值调用函数。在函数中的对形参的修改,不会在函数外体现。
- class Student
- {
- public:
- char Name[10];
- int Age;
- };
- void Edit(Student stu)
- {
- stu.Age = 30;
- }
- void CMFCTestDlg::OnBnClickedButton3()
- {
- Student s;
- sprintf(s.Name, "tom");
- s.Age = 29;
- Edit(s);
- //此处s.Age仍然为29
- }
2、传址调用函数。在函数中的对形参的修改,会在函数外体现。
- class Student
- {
- public:
- char Name[10];
- int Age;
- };
- void Edit(Student* stu)
- {
- Stu->Age = 30;
- //或者,下面这种写法也会影响函数外
- Student s;
- sprintf(s.Name, "jerry");
- s.Age = 33;
- *stu = s;
- }
- void CMFCTestDlg::OnBnClickedButton3()
- {
- Student s;
- sprintf(s.Name, "tom");
- s.Age = 29;
- Edit(&s);
- //此处s.Age仍然为30
- }
3、用指针参数去申请内存,那么应该改用“指向指针的指针”
- 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);
- }
4、用函数返回值来传递动态内存。这种方法更加简单和容易理解
- 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);
- }
5、用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。
- char *GetString(void)
- {
- char p[] = "hello world";
- return p; // 编译器将提出警告
- }
- void Test4(void)
- {
- char *str = NULL;
- str = GetString(); // str 的内容是垃圾
- cout<< str << endl;
- }
6、用函数返回值来返回“静态存储区”的指针。
- char *GetString2(void)
- {
- char *p = "hello world";
- return p;
- }
- void Test5(void)
- {
- char *str = NULL;
- str = GetString2();
- cout<< str << endl;
- }
上面示例6运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。