讲这个问题之前要先简单讲一下C++的内存管理。
更详细的内容可以参考这篇文章《C++内存管理》。
C++程序的内存被分为堆(heap),栈(stack),全局/静态存储区,*存储区和常量存储区。
1)堆(heap):new的都存放在这里,属于动态分配,如果在程序中调用delete释放掉,那么将一直存在,直到程序结束,由程序释放掉。
2)栈(stack):由系统自动分配,形参和局部变量均存储在这里,函数退出时该栈自动销毁。
3)全局/静态存储区:程序一开始执行即分配,生命周期持续到程序结束,存放全局变量,静态变量。
4)*存储区域:与堆的区别在于存放的是malloc的。
5)常量存储区:顾名思义,存放常量的存储区。
明白了C++的内存管理,接下来函数调用过程就比较好理解了。
注意:栈空间是从高地址向低地址增长的。所以压栈即表示栈顶指针变小,而出栈则相反。
函数调用者维护了一个栈空间(stack),拥有栈底指针ebp和栈顶指针esp。
调用函数时,栈变化过程简单描述如下:
1)先将函数返回地址ret压栈,即函数执行完毕后将从哪里继续执行下去
2)将ebp压栈
3)讲esp赋值给ebp
4)将函数局部变量压栈
函数执行完毕后,栈变化过程如下:
1)函数局部变量出栈
2)ebp出栈,恢复ebp的值
3)函数返回地址出栈
从以上的变化过程可以看到,函数调用者通过操作ebp和esp的值变化来维护栈的变化。即是,虽然函数执行完毕后栈“销毁”了,但在重入之前,存储在栈中的数据仍然存在!这个时候,通过指针来访问该位置仍然可以获得正确的值!
但是为什么不推荐使用这种方式来获得局部变量的值呢?原因也是显而易见的,栈随时会被重入!重入之后,对应位置的值变成了不可预测,访问它会导致不可预测的后果。
那在什么情况下可以通过函数返回指针的方式来访问函数局部变量呢?参照C++的内存管理,答案很明显,new的变量在函数结束之后仍然可以通过指针“安全”地访问!但这其实带来了新的问题:你要负责管理这个变量的生命周期或者任由它生存到程序结束(可能白白占用了内存!)
一个简单的例子如下:
- #include <iostream>
- using namespace std;
- int func1(int param)
- {
- int local_param = param;
- return local_param;
- }
- int* func2(int param)
- {
- int local_param = param;
- return &local_param;
- }
- int *func3(int param)
- {
- int *new_param = new int(param);
- return new_param;
- }
- int main()
- {
- int *pNewVar = func3(3);
- int *pVar = func2(2);
- cout << pVar << endl;
- cout << pNewVar << endl;
- cout << func1(1) << endl;
- cout << pVar << endl;
- cout << pNewVar << endl;
- return 0;
- }
全局变量
作用域:全局作用域(全局变量只需在一个源文件中定义,就可以作用于所有的源文件。)
生命周期:程序运行期一直存在
引用方法:其他文件中要使用必须用extern 关键字声明要引用的全局变量。
内存分布:全局数据区
注意:如果在两个文件中都定义了相同名字的全局变量,连接出错:变量重定义
例子:
+ expand sourceview plaincopy to clipboardprint?//defime.cpp
int g_iValue = 1;
//main.cpp
extern int g_iValue;
int main()
{
cout << g_iValue;
return 0;
}
//defime.cpp
int g_iValue = 1;
//main.cpp
extern int g_iValue;
int main()
{
cout << g_iValue;
return 0;
}
全局静态变量
作用域:文件作用域(只在被定义的文件中可见。)
生命周期:程序运行期一直存在
内存分布:全局数据区
定义方法:static关键字,const 关键字
注意:只要文件不互相包含,在两个不同的文件中是可以定义完全相同的两个静态变量的,它们是两个完全不同的变量
例子:
+ expand sourceview plaincopy to clipboardprint?const int iValue_1;
static const int iValue_2;
static int iValue_3;
int main()
{
return 0;
}
const int iValue_1;
static const int iValue_2;
static int iValue_3;
int main()
{
return 0;
}
静态局部变量
作用域:局部作用域(只在局部作用域中可见)
生命周期:程序运行期一直存在
内存分布:全局数据区
定义方法:局部作用域用中用static定义
注意:只被初始化一次,多线程中需加锁保护
例子:
+ expand sourceview plaincopy to clipboardprint?void function()
{
static int iREFCounter = 0;
}
void function()
{
static int iREFCounter = 0;
}
局部变量
作用域:局部作用域(只在局部作用域中可见)
生命周期:程序运行出局部作用域即被销毁
内存分布:栈区
注意:auto指示符标示
还有一点要说明,掌握static关键字的使用很关键。以下是引用别人的一些经验之谈:
Tips:
若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;
如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
来CSDN很久了,一直都想写点东西,今天终于出手了。水平有限,只能跟大家分享一些基础知识的总结,以共勉,共同提升。