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


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

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

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

    返回局部变量       

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

    可返回的局部变量:

    1、返回局部变量本身

    1. #include
    2. int display() {
    3. int num = 9;
    4. return num;
    5. }
    6. int main() {
    7. int p = display();
    8. std::cout << p << std::endl; // 9
    9. return 0;
    10. }

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

    返回局部变量地址

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

            举例如下:

    1. #include
    2. int* display() {
    3. int num[3] = {8,6,5};
    4. return num;
    5. }
    6. int main() {
    7. int* p = display();
    8. for(int i = 0; i < 3; i++) {
    9. std::cout << *(p+i) << std::endl;
    10. }
    11. return 0;
    12. }

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

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

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

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

    1. #include
    2. const char* display() {
    3. const char* buffer = "helloword";
    4. return buffer;
    5. }
    6. int main() {
    7. const char* str;
    8. str = display();
    9. std::cout << str << std::endl;
    10. return 0;
    11. }

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

    上述程序输出:

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

    1. #include
    2. char* display()
    3. {
    4. char buffer[] = "helloword";
    5. return buffer;
    6. }
    7. int main()
    8. {
    9. char* str;
    10. str = display();
    11. std::cout << str;
    12. return 0;
    13. }

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

    2、使用全局声明的数组

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

    1. #include
    2. int* display() {
    3. int num[5] = { 3,4,5,6,8 };
    4. return num;
    5. }
    6. int main() {
    7. int* p;
    8. p = display();
    9. for (int i = 0; i < 5; i++) {
    10. std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
    11. }
    12. return 0;
    13. }

    输出结果: 

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

    1. #include
    2. int num[5] = { 3,4,5,6,8 };
    3. int* display() {
    4. return num;
    5. }
    6. int main() {
    7. int* p;
    8. p = display();
    9. for (int i = 0; i < 5; i++) {
    10. std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
    11. }
    12. return 0;
    13. }

    输出结果:

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

    3、使用静态数组 static 

    1. #include
    2. int* display() {
    3. static int num[5] = { 3,4,5,6,8 };
    4. return num;
    5. }
    6. int main() {
    7. int* p;
    8. p = display();
    9. for (int i = 0; i < 5; i++) {
    10. std::cout << "*(p+" << i << "):" << *(p + i) << std::endl;
    11. }
    12. return 0;
    13. }

    输出结果: 

     

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

    补充:

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

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

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

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

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

    使用new动态分配内存 

    1. #include
    2. char* display()
    3. {
    4. char* buffer = new char[11];
    5. strcpy_s(buffer,11, "helloworld");
    6. return buffer;
    7. }
    8. int main()
    9. {
    10. char* str;
    11. str = display();
    12. std::cout << str;
    13. delete[] str; // 释放内存
    14. return 0;
    15. }

    输出结果为: 

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

     使用malloc动态分配内存

    1. #include
    2. char* fun()
    3. {
    4. int i;
    5. char* buffer = (char*)malloc(sizeof(char) * 20);
    6. if (buffer != NULL) {
    7. strcpy_s(buffer,20, "abcdefgwwwwwwweeeww");
    8. }
    9. return buffer;
    10. }
    11. int main()
    12. {
    13. char* str;
    14. str = fun();
    15. if (str != NULL) {
    16. std::cout << str << std::endl;
    17. free(str); // 释放内存
    18. }
    19. return 0;
    20. }

    输出结果: 

    部分引用自:

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

  • 相关阅读:
    『现学现忘』Docker基础 — 29、Docker容器数据卷的应用
    CVPR 2022上人脸识别相关的论文分类整理
    产品工程师工作的职责十篇(合集)
    手撕前端面试题【JavaScript】
    企业私域增长难题该如何破解?推荐快鲸scrm系统
    *p++ 与 (*p)++
    Redis学习笔记(一) 基础数据类型
    pandas修改index
    JAVA架构之路(三)
    全志V853平台Camera模块开发框架详解
  • 原文地址:https://blog.csdn.net/m0_48241022/article/details/133682442