在学习函数的时候,发现有些函数返回一个变量,有些返回引用,有些返回指向当前对象的指针,深入挖掘,发现这牵扯到内存相关的东西

1.内存

如下图:32位X86机器的内存布局图,内存主要分为栈、堆、BSS段、数据段、代码段5个段。

  • 代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。程序在被载入内存后,会被分为很多小的区(section),有一个rodata区(也就是常量区),常量区的数据内存中的位置为代码段,存放的数据为字符串、const修饰的常量(全局的或是静态的,局部的存放在栈中)、如Char* s=”Hello,World”,那么指针s所指向的字符串”Hello,World“存放在rodata区,而这个字符串的地址也就是指针s存放在数据段中(程序载入内存中为.data区)。再如,static char *const s=”hello,world";那么这时候不仅"hello,world"字符串存放在rodata区,指针s也同样

  • 数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量或者静态变量的一块内存区域。数据段属于静态内存分配。原则上数据段所对应的内存区数据是可以改变的。这里没有提到局部变量,这是因为局部变量一般都存放在栈中。*局部变量不管是否有const修饰都存放在栈中,例如char const lcp=“999”;字符窜"999"存放在代码段的rodata区,这个没有说的,而它对应的地址lcp指针存放在栈中;

  • BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。例如全局变量int i;静态变量static int si;都存放在这里面。

  • 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减),要注意的是,当分配的数据大小操作内核的限制时,内核采用匿名映射的方式实现而不是从堆中分配内存。

  • 栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段或代码段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

在进程被载入内存中时,基本上被分裂成许多小的节(section)。我们比较关注的是几个主要的节:
(1) .text 节
.text 节基本上相当于二进制可执行文件的.text部分,它包含了完成程序任务的机器指令。
该节标记为只读,如果发生写操作,会造成segmentation fault。在进程最初被加载到内存中开始,该节的大小就被固定。

(2).data 节
.data节用来存储初始化过的变量,如:全局int a =0 ; 该节的大小在运行时固定的。

(3).bss 节
栈下节(below stack section ,即.bss)用来存储为初始化的变量,如:int a; 该节的大小在运行时固定的。

(4) 堆节
堆节(heap section)用来存储动态分配的变量,位置从内存的低地址向高地址增长。内存的分配和释放通过malloc() 和 free() 函数控制。

(5) 栈节
栈节(stack section)用来跟踪函数调用(可能是递归的),在大多数系统上从内存的高地址向低地址增长。
同时,栈这种增长方式,导致了缓冲区溢出的可能性。

(6).rodata节
常量区,全局或静态const变量、指针存放区。

(7)环境/参数节
环境/参数节(environment/arguments section)用来存储系统环境变量的一份复制文件,
进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。
该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以使用该节。
另外,命令行参数也保持在该区域中

#include <stdlib.h>
/*全局已初始化数组,存放在数据段中*/
char a[100]="22222222";
/*全局未初始化数组,存放在BSS段*/
int b[100];
/*全局未初始化数据,存放在BSS段*/
int c;
/*全局已初始化字符串指针,字符串存放在代码段的.rodata区,他的地址p存放在数据段中*/
char * p="11111111";
/*全局已初始化整形常量,存放在代码段的.rodata区*/
const int ci=9;
/*全局已初始化指针常量,字符创和他的地址cp都存放在代码段的.rodata区*/
char *const cp="88888";  int main()
{  /*局部未初始化数据,存放在栈中*/  int li;  /*局部已初始化字符串指针,字符串内容存放在代码段的.rodata区,他的地址lp存放在栈*/  char *lp="66666";  /*局部已初始化数据,存放在栈中*/  char la[100]="4444444";  /*静态未初始化数据,存放在BSS段*/  static int si;  /*静态已初始化数据,存放在数据段在数据段中*/  static int sii=5;  /*局部常量存放在在栈中*/  const int lci=2;  /*局部已初始化字符串指针常量,字符串存放在代码段的.rodata区,他的地址lcp存放在栈中*/  char *const lcp="89999";  /*静态已初始化字符串指针常量,字符串和他的地址scp都存放在代码段的.rodata区*/  static char *const scp="kkkkk";  /*从堆中申请内存,mc存放在栈中*/  char *mc=(char*)malloc(100);  /*函数的参数以栈的形式传入,前面的字符串从代码段的.rodata区获得*/  printf("a[0]:%c\np[0]:%c\nla[0]:%c\nlp[0]:%c\n",a[0],p[0],la[0],lp[0]);  /*参数为字符串常量,从代码段的.rodata区取出*/  printf("hello world\n");  return 1;
}

C++编译器将计算机内存分为代码区和数据区,很显然,代码区就是存放程序代码,而数据区则是存放程序编译和执行过程出现的变量和常量。数据区又分为静态数据区、动态数据区,动态数据区包括堆区和栈区。

以下是各个区的作用:

(1)代码区:存放程序代码;
(2)数据区

  • a.静态数据区: 在编译器进行编译的时候就为该变量分配的内存,存放在这个区的数据在程序全部执行结束后系统自动释放,生命周期贯穿于整个程序执行过程。

  • b.动态数据区:包括堆区和栈区
    堆区:这部分存储空间完全由程序员自己负责管理,它的分配和释放都由程序员自己负责。这个区是唯一一个可以由程序员自己决定变量生存期的区间。可以用malloc,new申请对内存,并通过free和delete释放空间。如果程序员自己在堆区申请了空间,又忘记将这片内存释放掉,就会造成内存泄露的问题,导致后面一直无法访问这片存储区域。

    栈区:存放函数的形式参数和局部变量,由编译器分配和自动释放,函数执行完后,局部变量和形参占用的空间会自动被释放。效率比较高,但是分配的容量很有限。

注意:
1)全局变量以及静态变量存放在静态数据区;

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

2.return 返回值

在弄懂内存分配的问题过后,来看看函数调用的过程:

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

函数的返回值用于初始化在调用函数时创建的临时对象(temporary object),如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。

下面通过几个例子来谈谈内存分配和函数返回值的问题:

内存分配的问题:

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在静态区

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;
}

第一行输出和第三行输出的结果相同,而第一行、第三行与第二行输出的结果不同。从这里可以看出,当指针作为参数进行传递时传递的也只是一个值,只不过该值只一个地址,因此对于形参的改变并不影响实参。

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,有时候是乱麻。

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值的单元被重写数据,但是不会受到影响。

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!”的单元,并且该单元里的内容在程序没有执行完是不会被修改的,因此可以正确输出结果。

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在堆上申请的空间,这部分空间是由程序员自己管理的,如果程序员没有手动释放堆区的空间,那么存储单元里的内容是不会被重写的,因此可以正确输出结果。

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,防止指针在后面不小心又被使用,造成无法估计的后果。

2.return 返回引用

有些重载符需要返回引用:原因如下
https://www.cnblogs.com/codingmengmeng/p/5871254.html

来源:
【1】https://www.cnblogs.com/dolphin0520/archive/2011/04/04/2005061.html
【2】https://blog.csdn.net/bullbat/article/details/7318269
【3】https://www.cnblogs.com/fly1988happy/archive/2011/12/14/2286908.html
【4】https://www.cnblogs.com/codingmengmeng/p/5871254.html

c++ return返回值与内存相关推荐

  1. R语言return返回值的形式实战

    R语言return返回值的形式实战 目录 R语言return返回值的形式实战 #包含return的R函数 #不包含return的R函数

  2. return 返回值的问题

    def yue(): print("1. 打开手机") print("2. 打开陌陌") print("3. 找个漂亮的小姐姐") prin ...

  3. C#获取存储过程的 Return返回值和Output输出参数值

    一.不用SQLHelper.cs等帮助类 1.获取Return返回值  程序代码  存储过程 Create PROCEDURE MYSQL   @a int,   @b int AS   return ...

  4. 关于ExecuteNonQuery执行存储过程的返回值 、、实例讲解存储过程的返回值与传出参数、、、C#获取存储过程的 Return返回值和Output输出参数值...

    关于ExecuteNonQuery执行存储过程的返回值 用到过ExecuteNonQuery()函数的朋友们在开发的时候肯定这么用过. if(cmd.ExecuteNonQuery("xxx ...

  5. python return返回值_Python return语句 函数返回值

    return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天就来仔细的讲解一下. python 函数返回值 ...

  6. python没有return语句的函数将返回_为什么Python没有return返回值

    为什么Python没有return返回值 发布时间:2020-08-24 17:12:18 来源:亿速云 阅读:118 今天就跟大家聊聊有关为什么Python没有return返回值,可能很多人都不太了 ...

  7. 0717Python总结-return返回值,全局及局部变量,函数名的使用,函数的嵌套,nonlocal修改局部变量,及locals和globals

    一.return 返回值 自定义函数的返回值,return 可以把值返回到函数的调用处 (1) return + 六大标准数据类型 , 还有类和对象,函数 如果不定义return , 默认返回的是No ...

  8. Python ------ return返回值等

    杂 return 全局变量和局部变量 函数名的使用 函数的嵌套 nonlocal return # ### return 返回值 """ 自定义函数的返回值,return ...

  9. function 函数和return返回值

    function 函数和return返回值 可以用来封装代码,它里面的代码不会立即执行 需要使用 函数名() 来调用里面的代码 语法:function 函数名 ([形参1,形参2,.....形参n]) ...

最新文章

  1. 重磅:国家正式出台学术不端行为界定
  2. webbrowser控件 有数据 但页面空白_如何在Excel中实现可以切换不同数据系列的滚珠图?...
  3. java 动态代理深度学习(Proxy,InvocationHandler)
  4. mfc140dll 丢失 微软常用运行库_微软常用运行库合集 2020.9月(32amp;64位)
  5. SQL语句:从一个表里按年份统计条目数
  6. android中获取时间
  7. android 自定义View 的详细介绍
  8. python如何定义i_如何在Python中使用自定义消息引发相同的Exception?
  9. 解决tar命令出现“time stamp XXX in the future”的办法
  10. python基础编程语法-Python基础语法介绍:面向对象编程(上)
  11. 干货 | 一文掌握常用的机器学习模型
  12. fNIRS近红外数据处理过程
  13. Fedora9下codeblocks安装无法编译的问题
  14. Windows删除打开方式多余项
  15. 【非常简单bug管理工具-TAPD 】
  16. 微信小程序在智能家居物联网中的应用
  17. 【算法】动态规划 ④ ( 动态规划分类 | 坐标型动态规划 | 前缀划分型动态规划 | 前缀匹配型动态规划 | 区间型动态规划 | 背包型动态规划 )
  18. 软件著作权申请——使用了MIT-licence开源框架
  19. 高项、高级项目管理师论文-风险管理
  20. Linux——DNS篇

热门文章

  1. 汉阳新员工培训拒绝走马观花快乐在线打听工友有话讲
  2. Ubuntu下的图像编辑软件
  3. 三四十岁的大龄程序员,如何保持自己的职场核心竞争力?
  4. 系统安装报错:dracut-initqueue : Warning: dracut-initqueue timeout - starting timeout scripts
  5. Unity编辑器Bug----tranform.positon assign attempt for “Scene Camera”is not valid.
  6. S7-200SMART指针学习(二)指针的内部结构
  7. 利用gantt-elastic 实现简易项目甘特图
  8. SEIR模型及多染病仓室再生数的推导
  9. 大促系统全流量压测及稳定性保证——京东交易架构分享
  10. 接了个私活,甲方竟然让我教他写代码!