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

        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博客 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值