C/C++ 中的函数返回局部变量以及局部变量的地址?

news2024/11/25 8:19:48

        C/C++中,函数内部的一切变量(函数内部局部变量,形参)都是在其被调用时才被分配内存单元。形参和函数内部的局部变量的生命期和作用域都是在函数内部(static变量的生命期除外)。子函数运行结束时,所有局部变量的内存单元会被系统释放。在C中,函数被调用时的传参方式有两种形式:传值传址
        传址的好处:
(1) 能在函数内部通过实参地址间接地改变实参的值。
(2) 当所传实参内容比较庞大时,传址只是复制了整个实参的地址过去,指针依据同一个地址访问实参变量。而传值就会将实参内容整个拷贝过去,形参会跟实参占一样大的内存,栈空间是有限的。当然了,在弱小的程序中,传址的这个优点不会被体现出来。

        在函数中,可以随意的返回一个局部变量。
        但如果返回一个局部变量的地址(指针),编译器就会给出警告。因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放,这样指针指向的内容就是不可预料的内容,程序就会出错。

        准确的来说,函数不能返回指向栈内存的指针(返回指向堆内存的指针是可以的)。

返回局部变量       

        C++中,函数是可以返回局部变量的,原因:返回值是拷贝值,局部变量的作用域为函数内部,函数执行结束,栈上的局部变量会销毁,内存释放。

可返回的局部变量:

1、返回局部变量本身

#include <iostream>

int display() {
	int num = 9;
	return num;
}

int main() {
	int p = display();
    std::cout << p << std::endl; // 9
	return 0;
}

        display 函数返回一个 int 类型的局部变量 num,函数会把局部的num的值复制一份拷贝给主函数里面的 p 变量。这样是可以的,而且这种方式在程序里面还是经常用到的。
上面程序输出:9

返回局部变量地址

         引用一位博主的分析:https://blog.csdn.net/qq_34801642/article/details/88411252
        C/C++语言函数是不能返回局部变量地址(特指存放于栈区的局部变量地址),除非是局部静态变量地址,字符串常量地址、动态分配地址。其原因是一般局部变量的作用域只在函数内,其存储位置在栈区中,当程序调用完函数后,局部变量会随此函数一起被释放。其地址指向的内容不明(原先的数值可能不变,也可能改变)。而局部静态变量地址和字符串常量地址存放在数据区,动态分配地址存放在堆区,函数运行结束后只会释放栈区的内容,而不会改变数据区和堆区。

        举例如下:

#include <iostream>

int* display() {
	int num[3] = {8,6,5};
	return num;
}

int main() {
	int* p = display();
	for(int i = 0; i < 3; i++) {
		std::cout << *(p+i) << std::endl;
	}
    
	return 0;
}

        这段代码存在一个问题,即在函数display()中返回了一个指向局部变量num的指针。这是不安全的操作,因为当函数display()结束时,局部变量num将被销毁,指向它的指针p将变成悬空指针,使用它可能导致未定义的行为。
上述程序输出结果为:

若要完整的打印num数组中的3个数,我们该怎么做呢?

函数返回局部变量的地址通常有以下几种方法

1、返回一个字符串常量的指针
const char* buffer = "helloword";

#include <iostream>

const char* display() {
	const char* buffer = "helloword";
	return buffer;
}

int main() {
	const char* str;
	str = display();
	std::cout << str << std::endl;
	return 0;
}

        这样程序运行是没有问题的;buffer存在只读内存区,在 display() 退出的时候,字符串常量不会被收回,因此把地址赋给str时可以正确访问。
        上面这个方式只是最简单的解决方案,因为字符串存放在只读内存区,以后需要修改它的时候就会很麻烦。 

上述程序输出:

(以下为错误方法 char buffer[] = "helloword"; ) 

#include<iostream>
char* display()
{
	char buffer[] = "helloword";
	return buffer;
}

int main()
{
	char* str;
	str = display();
	std::cout << str;
	return 0;
}

        在display()函数中,你定义了一个局部字符数组buffer,并将其作为指针返回给主函数。然而,当函数执行完毕后,局部数组buffer的生命周期结束,它所占用的内存将被释放。因此,在主函数中访问指针str指向的值将会导致未定义的行为。
        运行上述程序,输出乱码。

2、使用全局声明的数组

 没有使用全局声明的数组的情况

#include <iostream>


int* display() {
	int num[5] = { 3,4,5,6,8 };
	return num;
}

int main() {
	int* p;
	p = display();
	for (int i = 0; i < 5; i++) {
		std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
	}
	return 0;
}

输出结果: 

 使用了全局声明的数组的情况

#include <iostream>

int num[5] = { 3,4,5,6,8 };
int* display() {
	return num;
}

int main() {
	int* p;
	p = display();
	for (int i = 0; i < 5; i++) {
		std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
	}
	return 0;
}

输出结果:

        这种情况简单容易。缺点就是任何人都有可能在任何时候修改这个全局数组,而且该函数的下一次调用会覆盖数组的内容。 

3、使用静态数组 static 

#include<iostream>


int* display() {
	static int num[5] = { 3,4,5,6,8 };
	return num;
}

int main() {
	int* p;
	p = display();
	for (int i = 0; i < 5; i++) {
		std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
	}
	return 0;
}

输出结果: 

 

        使用静态数组可以保证内存不被回收,而且可以防止任何人修改这个数组。只有拥有指向该数组的指针的函数才能修改这个静态数组,不过同时该函数的下一次调用会覆盖数组的内容。同时和全局数组一样,大型缓冲区闲置是非常浪费空间的。 

补充:

static int num[5]和int num[5]的区别在于变量的作用域和生命周期。

1. static int num[5]:在函数内部或者代码块内部使用static关键字声明的数组,称为静态数组。静态数组的特点是:
   - 作用域:静态数组的作用域限定在声明它的函数内部或者代码块内部。
   - 生命周期:静态数组在程序运行期间一直存在,即使函数执行完毕或者代码块结束,数组仍然存在于内存中。
   - 存储位置:静态数组存储在静态存储区,不会随着函数的调用而创建和销毁。

2. int num[5]:只使用int关键字声明的数组,称为自动数组(也称为局部数组)。自动数组的特点是:
   - 作用域:自动数组的作用域限定在声明它的函数内部或者代码块内部。
   - 生命周期:自动数组的生命周期与所在的函数执行周期相关,函数执行完毕或者代码块结束时,数组会被销毁。
   - 存储位置:自动数组存储在栈上,随着函数的调用和返回而动态创建和销毁。

总结:
静态数组的作用于是全局的,生命周期是整个程序运行期间,存储在静态存储区;
自动数组的作用域是局部的,生命周期与所在函数相关,存储在栈上。

4、显式的分配内存,在堆上动态分配内存 new / malloc

使用new动态分配内存 


#include<iostream>
char* display()
{
    char* buffer = new char[11];
    strcpy_s(buffer,11, "helloworld");
    return buffer;
}

int main()
{
    char* str;
    str = display();
    std::cout << str;

    delete[] str; // 释放内存
    return 0;
}


输出结果为: 

        在上述代码中,我们使用strcpy_s函数来进行字符串复制操作。注意,我们还将缓冲区大小作为第二个参数传递给strcpy_s,确保不会发生缓冲区溢出。
        注意,在使用动态内存分配时,需要确保在不再使用时手动释放内存,以避免内存泄漏。

 使用malloc动态分配内存


#include<iostream>
char* fun()
{
    int i;
    char* buffer = (char*)malloc(sizeof(char) * 20); 
    if (buffer != NULL) {
        strcpy_s(buffer,20, "abcdefgwwwwwwweeeww");
    }
    return buffer;
}

int main()
{
    char* str;
    str = fun();
    if (str != NULL) {
        std::cout << str << std::endl;
        free(str); // 释放内存
    }
    return 0;
}

输出结果: 

部分引用自:

C/C++ 返回函数内局部变量和局部变量的地址_c++返回地址的函数-CSDN博客 

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

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

相关文章

帮助文档Api

帮助文档Api 按照帮助文档的使用步骤学习Scanner类的使用&#xff0c;并实现键盘录入一个字符串&#xff0c;最后输出在控制台 部分Scanner的api文档如下&#xff1a; package com.api.Demo01;// 需要导入 Scanner包 import java.util.Scanner;public class Test01 {public sta…

千兆以太网传输层 UDP 协议原理与 FPGA 实现(UDP接收)

文章目录 前言心得体会一、 UDP 协议简单回顾二、UDP接收实现三、完整代码展示四、仿真测试(1)模拟电脑数据发送,(2)测试顶层文件编写(3)仿真文件(4)仿真波形前言 在前面我们对以太网 UDP 帧格式做了讲解,UDP 帧格式包括前导码+帧界定符、以太网头部数据、IP 头部数…

Ride基本使用

基本使用步骤&#xff1a; 创建项目(New Project)创建模块(New Directory)创建测试套件(New Suite)创建测试用例(New Test Case)创建资源文件(New Resource)创建业务关键字(New User Keyword)常用关键字用例编写robot报告显示 创建项目(New Project) File --> New Project…

程序无法启动,提示“找不到msvcp140.dll”或“msvcp140.dll缺失报错”解决方法

大家好&#xff01;今天我来给大家分享一下msvcp140.dll丢失的解决方法。我们都知道&#xff0c;在运行一些软件或游戏时&#xff0c;经常会遇到“找不到msvcp140.dll”的错误提示&#xff0c;这会让我们非常苦恼。那么&#xff0c;这个问题该怎么解决呢&#xff1f;下面我将为…

如何理解分布式锁?

分布式锁的实现有哪些&#xff1f; 1.Memcached分布式锁 利用Memcached的add命令。此命令是原子操作&#xff0c;只有在key不存在的情况下&#xff0c;才能add成功&#xff0c;也就意味着线程得到了锁。 2.Reids分布式锁 和Memcached的方式类似&#xff0c;利用Redis的setn…

“益路同行”栏目专访第12期——泰格智能AI英语·李勤骞老师

中国善网在本届&#xff08;第十届&#xff09;慈展会上特别推出了《益路同行》采访栏目&#xff0c;《益路同行》栏目旨在寻觅公益之路上同行者的故事&#xff0c;挖掘公益更深层次的内涵&#xff0c;探索新时代公益发展道路。希望公益企业、人物、故事被更多人看到&#xff0…

HEC-RAS 1D/2D水动力与水环境模拟技术案例实践及拓展应用

水动力与水环境模型的数值模拟是实现水资源规划、环境影响分析、防洪规划以及未来气候变化下预测和分析的主要手段。然而&#xff0c;一方面水动力和水环境模型的使用非常复杂&#xff0c;理论繁复&#xff1b;另一方面&#xff0c;免费的水动力和水环境软件往往缺少重要功能&a…

蓝桥杯 字符串和日期

有一个类型的题目是找到输出图形的规律&#xff0c;然后将其实现。观察下面的图形。你想想你该怎么输出这个图形呢? ABBB#include<stdio.h> int main(){printf(" A\n");printf("BBB\n");return 0; }那么&#xff0c;对于如下的图形&#xff1a; ABB…

9 家用热水器用户行为分析与事件识别

第9章 家用热水器用户行为分析与事件识别 9.1 了解家用热水器用户行为分析的背景与步骤9.1.1 热水器采集数据基本情况9.1.2 熟悉家用热水器用户行为分析的步骤与流程 9.2 预处理热水器用户用水数据9.2.1 删除冗余特征9.2.2 划分用水事件9.2.3 确定单次用水事件时长阈值9.2.4 代…

C++并发与多线程(4) | 传递临时对象作为线程参数的一些问题Ⅰ

一、陷阱1 写一个传递临时对象作为线程参数的示例: #include <iostream> #include <vector> #include <thread> using namespace std;void myprint(const int& i, char* pmybuf) {cout << i << endl;cout << pmybuf << endl;r…

Python 无废话-基础知识列表list详讲

本文详细的介绍了在Python中如何处理list数据类型&#xff0c;涉及了Python 基础知识列表list 详讲&#xff0c;包含list 定义、遍历循环、元素获取、切片、添加、删除、查找元素&#xff0c;以后列表在函数、copy中使用。 列表List数据类型 列表特征 1) 列表中的各个元素&…

06. 机器学习入门2 - 理解特征和向量

文章目录 机器学习初探特征和向量机器学习的通用框架梯度下降 Hi, 你好。我是茶桁。 上一节课&#xff0c;咱们用一个案例引入了机器学习的话题&#xff0c;并跟大家讲了一下「动态规划」。 那这节课&#xff0c;我们要真正进入机器学习。 机器学习初探 在正式开始之前&…

什么是CAS机制?

CAS和Synchronized的区别是什么&#xff1f;适合什么样的场景&#xff1f;有什么样的优点和缺点&#xff1f; 示例程序&#xff1a;启动两个线程&#xff0c;每个线程中让静态变量count循环累加100次。 public class ThreadTest {private static int count 0;public static …

数据统计--图形报表--ApacheEcharts技术 --苍穹外卖day10

Apache Echarts 营业额统计 重点:已完成订单金额要排除其他状态的金额 根据时间选择区间 设计vo用于后端向前端传输数据,dto用于后端接收前端发送的数据 GetMapping("/turnoverStatistics")ApiOperation("营业额统计")public Result<TurnoverReportVO…

CamoDroid 与 Frida Android动态分析工具搭建流程(linux)

CamoDroid 与 Frida Android动态分析工具搭建流程&#xff08;linux&#xff09; 写在前面 这个东西配置起来比较复杂&#xff0c;其实最主要就是配置frida&#xff0c;如果你之前就使用过frida框架的话问题就不是很大 介绍camodroid CamoDroid 是一个开源和开放架构的 And…

图片素材免费下载,高清无水印,无需担心版权问题。

找图片素材就上这8个网站&#xff0c;免费可商用&#xff0c;建议收藏起来~ 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 网站主要为新手设计师提供免费素材&#xff0c;这些素材的质量都很高&#xff0c;类别也很多&#xff0c;像平面、UI、电商、视频、图片…

JVM命令行监控工具

JVM命令行监控工具 概述 性能诊断是软件工程师在日常工作中需要经常面对和解决的问题&#xff0c;在用户体验至上的今天&#xff0c;解决好应用的性能问题能带来非常大的收益。 Java作为最流行的编程语言之一&#xff0c;其应用性能诊断一直受到业界广泛关注&#xff0c;可能…

【Spring MVC研究】MVC如何浏览器请求(service方法)

文章目录 1. DispatcherServlet 的 service 方法1.1. processRequest 方法1.2. doService 方法 背景&#xff1a;平时我们学习 MVC 重点关注的时DispatcherServlet 的 doDispatcher 方法&#xff0c;但是在 doDispatcher 方法之前 还有请求处理的前置过程&#xff0c;这个过程…

B站涨粉,UP主涨粉技巧有哪些?

B 站作为深受年轻人喜爱的社交媒体平台&#xff0c;是一个不断生产优质内容的多元化文化社区&#xff0c;有不少视频出圈。随着视频行业的成熟发展&#xff0c;视频创作者们的竞争愈发激烈&#xff0c;不少创作者有涨粉难&#xff0c;曝光差的困扰。而UP主连续不断的涨粉不仅取…

CS5210芯片设计|CS5210设计方案|HDMI转VGA方案|

CS5210方案应用&#xff0c;CS5210方案设计&#xff0c;HDMI转VGA方案&#xff0c;国产集睿致远研发CS5210 HDMI到VGA转换器结合了HDMI输入接口和模拟RGB DAC输出。支持内部LDO&#xff0c;节省成本&#xff0c;优化电路板空间。CS5210适用于各种市场和显示应用程序&#xff0c…