在学习算法与数据结构图论时,按照视频课程写代码却出现了问题:
void _Path(int node, vector<int> vec)
{
stack<int> s;
int p = node;
while(p != -1)
{
s.push(p);
p = from[p];
}
vec.clear();
while(!s.empty())
{
vec.push_back(s.top());
s.pop();
}
}
void ShowPath(int node)
{
vector<int> vec;
_Path(node, vec);
for (size_t i=0; i<vec.size(); i++)
{
cout<<vec[i];
if (i < vec.size()-1)
cout<<"->";
else cout<<endl;
}
}
最开始vec没用设置为引用,因此在ShowPath中,虽然调用了_Path(),但由于形参是直接传进去的,调用完_Path后vec的内存空间就释放了,因此vec.size()=0,
详细内存分布原理为(转):
理解局部变量和全局变量的内存问题核心是理解编译器在主函数和子函数调用执行过程中是如何管理分配内存的。
内存中数据区被分为动态数据区与静态数据区。其中静态数据区可以简单理解为写在main函数与其他函数外部的全局变量存储的区域,程序运行时,编译器为其在这个区域内分配内存,其生命周期贯穿整个程序执行过程。
这里我们主要讲讲动态数据区,动态数据区中主要分为heap与stack。假设下图为内存区域,其中的堆区和栈区分别具有基地址;堆区和栈区的内存分配都是先从基地址开始分配,并在内存释放后指针再次回到基地址。这里红色边框区域为堆区,蓝色为栈区;其中主函数中的变量在堆区分配,而主函数中调用的函数内部的局部变量在栈区分配,其生命周期为整个子函数调用期。以下面最简单的小程序的执行为例:
堆基地址 |
(栈基地址) |
1
2
3
4
5
6
7
8
9
10
11
12
|
void
f(
int
c)
{
int
e;
int
f;
}
int
main()
{
int
a=1;
int
b=2;
f(a);<br>
int
d=3;<br> f(b);
return
0;
}
|
首先程序从主函数开始执行,执行语句int a;int b;此时编译器在堆区依次为其分配内存。
b=2 |
堆基 a=1 |
f |
e |
栈基 c |
当第一次调用子函数f时, c,e,f依次进栈(图中省略了栈顶指针);
当第一次子函数f调用后,栈区内存被释放,相当于f,e,c依次出栈,栈顶指针回到基地址,而此时,堆区内存并未释放,因为主函数还没有结束。int d=3;执行之后:
d=3 |
b=2 |
堆基 a=1 |
栈基 |
此时栈区为空,当再次执行子函数f时,如同第一次一样,编译器再次在栈区为其变量分配内存,从而局部变量进栈。f调用结束,栈区内存释放。在递归算法的执行过程中,函数不断调用自身,编译器为每个子函数开辟栈区空间,其实现类似于此。当主函数结束后,堆区的内存才被释放。
大多数时候,编译器在编译时在内存中做了很多工作,我们不能从代码本身了解内存分配,例如理解 i++ 语句与i=i+1的区别,乍一看一样,无非是给i 加1,但内存中却有本质的区别,i=i+1 执行过程中,编译器首先会将i+1的值保存在一个临时变量中,这个临时变量编译器自动申请,然后再把这个临时变量赋值给i , 而i++则直接给i加1;