浅谈C++中内存分配、函数调用和返回值问题

在谈述函数调用和返回值问题之前,先来看看C++中内存分配的问题。

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

以下是各个区的作用:

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

(2)数据区

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

   b.动态数据区:包括堆区和栈区

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

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

 

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

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

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

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

 

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

内存分配的问题:

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

 

1.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;
}
复制代码


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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/351607.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dataguard从库数据库丢失恢复例子(模拟所有的控制文件)

1.退出日志应用模式[oraclelocalhost ~]$ sqlplus /nolog SQL*Plus: Release 11.2.0.4.0 Production on Mon Jan 14 16:09:16 2019 Copyright (c) 1982, 2013, Oracle. All rights reserved. SQL> connect / as sysdbaConnected.SQL> alter database recover managed sta…

python教程自带数据库_python教程自带数据库 | Python学哪个数据库

python使用数据库增条数据import pymysql#返回Connection#host"localhost"con pymysql.connect(host"192.168.31.28",port3306,user"atguigu",password"atguigu",db"atguigudb",charset"utf8")#返回cursor对象cu…

Java SE 8新功能介绍:使用新的DateTime API计算时间跨度

使用Java SE 8新的DateTime API JSR 310…可以实现更清晰&#xff0c;可读且功能强大的编码。 Java SE 8&#xff0c;JSR 310 在上一篇文章“ 使用Streams API处理集合 ”中&#xff1b; 我已深入探讨和探索如何使用流遍历集合&#xff0c;如何从集合和数组创建流以及最终汇总…

使用VS2012内建的C++测试架构进行单元测试

前言 在软件开发的过程中&#xff0c;单元测试(Unit testing)是一个重要的coding步骤&#xff0c;可以让你的程序代码质量大幅提升、协助你进行良好的程序架构设计&#xff0c;单元测试是针对程序单元(软件设计的最小单位)来进行正确性检验的测试工作&#xff0c;在程序化编程中…

【博 客 文 章】

1、C#中的Explicit和Implicit转载于:https://www.cnblogs.com/kikyoqiang/p/10270416.html

sql intersect mysql_SQL INTERSECT子句

SQL INTERSECT子句/操作符用于合并两个SELECT语句&#xff0c;但只从第一个SELECT语句返回完全相同于第二个SELECT语句结果的所有行。这意味着INTERSECT是由两个SELECT语句返回相同的行(唯一)。正如使用UNION操作&#xff0c;同样的规则可使用在INTERSECT运算符。 但MySQL不支持…

vs2010下release版本调试设置

设置在Release模式下调试的方法&#xff1a; 1.工程项目上右键 -> 属性 2.c -> 常规 -〉调试信息格式 选 程序数据库(/Zi)或(/ZI), 注意&#xff1a;如果是库的话&#xff0c;只能(Zi) 3.c -> 优化 -〉优化 选 禁止&#xff08;/Od&#xff09; 4.连接…

控制反转 java_控制反转( Ioc)快速入门

2.1 什么是控制反转(IOC&#xff1a;Inverse of Control)IOC反转控制&#xff0c;实际上就是将对象的创建权交给了Spring&#xff0c;程序员无需自己手动实例化对象。可以看出来工厂的作用就是用来解耦合的&#xff0c;而在使用spring的过程中&#xff0c;spring就是充当这个工…

spring 工作流引擎_带Spring的简单工作流引擎

spring 工作流引擎几个月前&#xff0c;在处理一个公司项目时&#xff0c;我们需要开发REST服务&#xff0c;该服务用于根据客户端应用程序发送的数据发送电子邮件。 在开发此服务期间&#xff0c;我们决定创建简单的工作流引擎&#xff0c;该引擎将为发送电子邮件收费&#xf…

如何成为一个牛逼的C/C++程序员? (仔细阅读)

这个题目的噱头太大&#xff0c;要真的写起来&#xff0c; 足够写一本书了。 本人是过来人&#xff0c; 结合自身的体会和大家交流一下&#xff0c;希望新人能少走弯路。 每个人的情况不一样&#xff0c;我下面的描述可能并不适合每一个看到这篇文章的人。 一、C/C语言 如果你的…

java 两个字段排序,如何在Java中按两个字段排序?

I have array of objects person (int age; String name;).How can I sort this array alphabetically by name and then by age?Which algorithm would you use for this ?解决方案You can use Collections.sort as follows:private static void order(List persons) {Colle…

数据结构java语言描述朱战立_数据结构——树(Java语言描述)

树根:rootNode. 树只有一个树根。节点:Node. 树上的所有节点。子节点数组:Node[]. 数组代表每个节点的所有子节点父节点:parentNode. 每个节点只有一个父节点。2. Node为Tree中的内部类parent &#xff1a;指向父节点的引用childern: 孩子数组&#xff0c;存储该节点的所有子节…

centos7.4进入单用户模式

centos7.4进入单用户模式 1 - 在启动grub菜单&#xff0c;选择编辑选项启动2 - 按键盘e键&#xff0c;来进入编辑界面 3 - 找到Linux 16的那一行&#xff0c;将ro改为rw init/sysroot/bin/sh 4 - 现在按下 Controlx &#xff0c;使用单用户模式启动 5 - 现在&#xff0c;可以使…

判断某程序是64位还是32位

1. 用代码判断本身if (IntPtr.Size 4) {// 32-bit}else if (IntPtr.Size 8){// 64-bit}2. 用代码判断正在运行的其他进程http://stackoverflow.com/questions/1953377/how-to-know-a-process-is-32-bit-or-64-bit-programmatically3. 不用代码判断任意exe&#xff0c;看第二个…

MySQL 中 NULLIF 、IFNULL、IF 的用法和区别

在 MySQL 中&#xff0c;NULLIF、IFNULL 和 IF 是用于处理 NULL 值的三种不同的函数。 1. NULLIF 函数 NULLIF 函数用于比较两个表达式&#xff0c;如果它们的值相等&#xff0c;则返回 NULL&#xff0c;否则返回第一个表达式的值。语法如下&#xff1a; NULLIF(expr1, expr2…

32和64位jvm_我应该使用32位还是64位JVM?

32和64位jvm这是我在企业软件开发生涯中多次遇到的问题。 我不得不每隔一段时间就提供有关配置特定新环境的建议。 而且&#xff0c;很多时候&#xff0c;手头的问题与“我应该使用32位或64位JVM”有关。 老实说&#xff0c;一开始我只是掷硬币。 而不是给出合理的答案。 &…

spring java code配置_Spring-09-使用Java的方式配置Spring

9. 使用Java的方式配置Spring我们现在要完全不使用Spring的xml配置&#xff0c;全权使用Java来配置Spring&#xff01;JavaConfig是Spring的一个子项目&#xff0c;在Spring4之后&#xff0c;他成为了一个核心功能。实体类&#xff1a;public class User {private String name;…

pythone函数基础(11)读,写,修改EXCEL

#读EXCEL需要导入xlrd模块---在python控制台pip install xlrd模块import xlrdbook xlrd.open_workbook(stu3.xls)sheet book.sheet_by_index(0)# sheet book.sheet_by_name(sheet1)# print(sheet.cell(0,0).value)#获取指定单元格的内容# print(sheet.cell(1,0).value)# pri…

强大的vim配置文件,让编程更随意

花了很长时间整理的&#xff0c;感觉用起来很方便&#xff0c;共享一下。 我的vim配置主要有以下优点&#xff1a; 1.按F5可以直接编译并执行C、C、java代码以及执行shell脚本&#xff0c;按“F8”可进行C、C代码的调试 2.自动插入文件头 &#xff0c;新建C、C源文件时自动插…

java工程转maven工程_将java工程转换为Maven工程

1、创建一个Java工程&#xff0c;如下图所示&#xff1a;2、选中此工程 -> 右键 -> Configure -> Convert to Maven project。出现如下的截图&#xff1a;3、点击之后会弹出对话框&#xff0c;如下图所示&#xff1a;4、直接点击完成即可&#xff0c;此时会在java工程…