函数调用过程解析

func里的a存储在调用fun函数时开辟的栈空间里,这块栈只在调用func时对func可用,调用结束后返回的a,其实是暂存在寄存器里的(一般情况下是eax),而返回到main里时,main又会把返回的值拷贝到自己所有的栈空间里(在这里是以临时变量的形式)。所以不管是func还是main,任何非static、register变量和常量的存储都是在函数局部存储区里(也就是对当前调用函数可见的栈空间)。main也是一个函数。


1、函数调用完就收回;
2、不是,栈是一段公共内存,函数的代码也不是存在栈里的,只是从栈上给新调用的函数分配一段栈空间,用来保存这个函数执行期间用到的局部变量;
3、函数的返回是被保存在寄存器里的(这个返回指的是return,不包括通过参数返回或者全局变量),栈空间由程序自动维护,函数退出以后栈的内容其实不会更改,只是栈指针复位,所以函数内部的局部变量声明了如果不赋值,它的值就是随机的也就是这个道理。

内存分配、函数调用和返回值问题

  • 首先,常量与变量
变量:在程序中可以改变值的量;常量:其值不能改变;
  • 内存分配
C++编译器将计算机内存分为:
代码区:代码区就是存放程序代码
数据区:数据区就是存放程序编译与执行过程中出现的变量与常量 
静态数据区:在编译器进行编译的时候就为该变量分配的内存,存放在这个区的数据在程序全部执行结束后系统自动释放,生命周期贯穿于整个程序执行过程;全局变量以及静态变量存放在静态数据区。
动态数据区:
  堆区:这部分的分配与释放由程序员自己管理,这是唯一一个由程序员自己决定变量生存周期的区间。可以用malloc,new,calloc,free,delete 等申请释放内存,如果程序员申请了空间,而忘记释放空间,会造成内存泄露,导致该内存段一直被占用而无法利用。
  栈区:存放函数的形参和局部变量,由编译器分配和自动释放,函数执行完后,局部变量和形参占用的空间会自动释放,效率比较高但是分配的容量有限

注意:常量的存放区域通常在程序区(程序区是只读,因此任何修改常量的行为都是非法的)。有的系统,也将部分常量分配到静态数据区,比如字符串常量(有的系统也将其分配在程序区)。但是要记住一点,常量所在的内存空间都是受系统保护的,不能修改。对常量空间的修改将造成访问内存出错,一般系统都会提示。常量的生命周期一直到程序执行结束为止。

补充:字符串存储时,如果用了SSO技术,小于16个字节存在栈上,大于16个字节存在堆上。(在于编译器版本,vs2010、clanglibc、linux libc++5之后的版本都已经使用了SSO)

字符串常用的两个技术:写时复制COW短字符串优化SSO。facebook也开发了自己fbstring技术。有兴趣可以学习下。

  • 函数调用过程
执行某个函数时,如果有参数,则在栈上为形式参数分配空间(如果是引用类型的参数则类外),继续进入到函数体内部,如果遇到变量,则按情况为变量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则直接返回调用该函数的地方(即执行远点),如果存在返回值,则先将返回值进行拷贝传回,再返回执行远点,函数全部执行完毕后,进行退栈操作,将刚才函数内部在栈上申请的内存空间释放掉。
  • 内存分配和函数返回值的例子:

1. 内存分配问题:根据实际情况安排内存位置若该变量为全局变量,则存放在静态数据区,这里指该变量为局部变量

static int b=0; // b在静态区
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指向的空间在堆区

  

2.test1

#include<iostream>
using namespace std; void test(int *p)
{
int b=2;
p=&b;
cout<<p<<endl;
} int main(void)
{
int a=10;
int *p=&a;
cout<<p<<endl;
test(p);
cout<<p<<endl;
return 0;
}
第一行输出和第三行输出的结果相同,与第二行输出的结果不同。
从这里可以看出,当指针作为参数进行传递时传递的也只是一个值,只不过该值只一个地址,因此对于形参的改变并不影响实参。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值(这里是在说实参指针本身的地址值不会变)。
 

3. test2

#include<iostream>
using namespace std; char* test(void)
{
char str[]="hello world!";
return str;
} int main(void)
{
char *p;
p=test();
cout<<p<<endl;
return 0;
}

输出结果可能是hello world!,也可能是乱码。

出现这种情况的原因在于:在test函数内部声明的str数组以及它的值"hello world”是在栈上保存的,当用return将str的值返回时,将str的值拷贝一份传回,当test函数执行结束后,会自动释放栈上的空间,即存放hello world的单元可能被重新写入数据,因此虽然main函数中的指针p是指向存放hello world的单元,但是无法保证test函数执行完后该存储单元里面存放的还是hello world,所以打印出的结果有时候是hello world,有时候是乱码。

4.test3

#include<iostream>
using namespace std; int test(void)
{
int a=1;
return a;
} int main(void)
{
int b;
b=test();
cout<<b<<endl;
return 0;
}

输出结果为 1

有人会问为什么这里传回来的值可以正确打印出来,不是栈会被刷新内容么?是的,确实,在test函数执行完后,存放a值的单元是可能会被重写,但是在函数执行return时,会创建一个int型的临时变量,将a的值复制拷贝给该临时变量,因此返回后能够得到正确的值,即使存放a值的单元被重写数据,但是不会受到影响。

5. test4

#include<iostream>
using namespace std; char* test(void)
{
char *p="hello world!";
return p;
} int main(void)
{
char *str;
str=test();
cout<<str<<endl;
return 0;
}

执行结果是 hello world!

同样返回的是指针,为什么这里会正确地打印出hello world1?这是因为char *p="hello world!",指针p是存放在栈上的,但是"hello world!”是一个常量字符串,因此存放在常量区,而常量区的变量的生存期与整个程序执行的生命期是一样的,因此在test函数执行完后,str指向存放“hello world!”的单元,并且该单元里的内容在程序没有执行完是不会被修改的,因此可以正确输出结果。

6.test5

#include<iostream>
using namespace std; char* test(void)
{
char *p=(char *)malloc(sizeof(char)*100);
strcpy(p,"hello world");
return p;
} int main(void)
{
char *str;
str=test();
cout<<str<<endl;
return 0;
}

运行结果 hello world.

这种情况下同样可以输出正确的结果,是因为是用malloc在堆上申请的空间,这部分空间是由程序员自己管理的,如果程序员没有手动释放堆区的空间,那么存储单元里的内容是不会被重写的,因此可以正确输出结果

7.test6

#include<iostream>
using namespace std; void test(void)
{
char *p=(char *)malloc(sizeof(char)*100);
strcpy(p,"hello world");
free(p);
if(p==NULL)
{
cout<<"NULL"<<endl;
}
} int main(void)
{
test();
return 0;
}

没有输出 .

在这里注意了,free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重 要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后应把指针指向NULL,防止指针在后面不小心又被使用,造成无法估计的后果。

结论:

1. 在返回值中,确实有拷贝的一说,但是拷贝的是返回变量的值,拷贝的是比如指针或实值,若是指针,其指向的内存是否被释放,若是释放则其在调用的主函数处,得到的是乱码。

2. 堆区域,程序员释放的是指针指向的内存,而不是指针变量本身。

3. 当指针作为参数进行传递时传递的也只是一个值,只不过该值只一个地址,因此对于形参的改变并不影响实参。

本质:

值传递:传递的是一个值,每次都需要拷贝。

引用传递:传递的是地址,不会进行拷贝。

c++之函数值传递和引用传递解析----关键在于理解函数return的实现机制(内存分配)的更多相关文章

  1. python函数值传递还是引用传递

    c/c++中有值传递引用传递的区别.但是python中是值传递还是引用传递呢?首先看python中对变量的定义 "python中变量是指向某个内存的, 而内存中的内容是不可变的." ...

  2. python中变量在内存中的存储与地址关系解析、浅度/深度copy、值传递、引用传递

    ---恢复内容开始--- 1.变量.地址 变量的实现方式有:引用语义.值语义 python语言中变量的实现方式就是引用语义,在变量里面保存的是值(对象)的引用(值所在处内存空间的地址).采用这种方式, ...

  3. JavaScript 函数参数传递到底是值传递还是引用传递

    tips:这篇文章是听了四脚猫的js课程后查的,深入的理解可以参看两篇博客: JavaScript数据类型--值类型和引用类型 JavaScript数据操作--原始值和引用值的操作本质 在传统的观念里 ...

  4. java 传参方式--值传递还是引用传递

    java 传参方式--值传递还是引用传递 参数是按值而不是按引用传递的说明 Java 应用程序有且仅有的一种参数传递机制,即按值传递.写它是为了揭穿普遍存在的一种神话,即认为 Java 应用程序按引用 ...

  5. 晨叔技术晨报: 你真的搞懂JS中的“值传递”和“引用传递”吗?

    晨叔周刊,每周一话题,技术天天涨. 本周的话题是JS的内存问题(加入本周话题,请点击传送门). 图 话题入口 今天的技术晨报来,就来谈谈JS中变量的,值传递和引用传递的问题.现在,对于很多的JSer来 ...

  6. Java中引用类型变量,对象,值类型,值传递,引用传递 区别与定义

    一.Java中什么叫做引用类型变量?引用:就是按内存地址查询       比如:String s = new String();这个其实是在栈内存里分配一块内存空间为s,在堆内存里new了一个Stri ...

  7. java中值传递和引用传递

    最近工作中使用到了值传递和引用传递,但是有点懵,现在看了下面的文章后清晰多了.一下是文章(网摘) 1:按值传递是什么 指的是在方法调用时,传递的参数是按值的拷贝传递.示例如下: public clas ...

  8. Java中的值传递和引用传递

    这几天一直再纠结这个问题,今天看了这篇文章有点思路了,这跟C++里函数参数为引用.指针还是有很大区别. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里 ...

  9. java的值传递和引用传递

    昨天博主在对于值传递和引用传递这里栽了一个大坑啊,导致一下午时间都浪费在这里,我们先说下值传递和引用传递java官方解释: 值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对 ...

随机推荐

  1. Android 设定横屏,禁止屏幕旋转,Activity重置 [更新视频播放器相关]

    1. 设定屏幕方向 当指定了屏幕的方向后(非SCREEN_ORIENTATION_UNSPECIFIED),屏幕就不会自己主动的旋转了 有2中方式控制屏幕方向: 1.1 改动AndroidManife ...

  2. Linux以下基于TCP多线程聊天室(server)

    接上篇博文,本文是server端的实现,主要实现的功能,就是现实client的连接.转发client发送的消息.以及client掉线提示等功能,同一时候能够在这这上面扩展和TCP以及线程相关的功能木块 ...

  3. python读取txt、csv和excel文件

    一.python读取txt文件:(思路:先打开文件,读取文件,最后用for循环输出内容) fp = open('test.txt','r') lines = fp.readlines() fp.clo ...

  4. PC和手机怎么实现绝对居中?

    示例1(懒人之家): http://www.51xuediannao.com/js/nav/360buy_nav.html 示例2(google官方):

  5. Unity3d载入外部图片文件

    unity里的图片在生成时会压缩成资源文件,有时客户想自己放一些图片用unity显示,就必须载入外部图片. 大体思路:用Application.streamingAssetsPath或Applicat ...

  6. Heap &amp; Priority Queue

    Heap & Priority Queue Definition & Description: In computer science/data structures, a prior ...

  7. RSA、AES加密解密

    RSA #!/usr/bin/env python # -*- coding:utf-8 -*- import rsa import base64 # ######### 1. 生成公钥私钥 #### ...

  8. Python 模块之 ConfigParser: 用 Python 解析配置文件

    在程序中使用配置文件来灵活的配置一些参数是一件很常见的事情,配置文件的解析并不复杂,在 Python 里更是如此,在官方发布的库中就包含有做这件事情的库,那就是 ConfigParser,这里简单的做 ...

  9. AtomicInteger在实际项目中的应用

    AtomicInteger.一个提供原子操作的Integer的类. 在Java语言中,++i和i++操作并非线程安全的.在使用的时候,不可避免的会用到synchronized关键字. 而AtomicI ...

  10. bash的几个特殊参数和位置参量

    http://blog.csdn.net/jiankun_wang/article/details/4336285 一.$*和$@ 首先介绍两个极其相似.很难区分的特殊参数$*和$@,先看如下输出: ...