C++中的作用域和返回值

32

我重新开始学习C++,并思考变量的作用域问题。如果我在一个函数内部定义一个变量,然后返回该变量,那么当它被返回时,它不会因为其所处的作用域已经结束而“死亡”吗?

我尝试过编写返回字符串的函数,并且确实是有效的。有人能解释一下这个问题吗?或者至少指点我一下可以解释这个问题的地方。

谢谢

6个回答

55
当函数终止时,将按以下步骤进行:
  • 将函数的返回值复制到为此目的在栈上放置的占位符中。

  • 弹出堆栈帧指针后的所有内容。这会销毁所有本地变量和参数。

  • 弹出堆栈中的返回值,并将其分配为函数的值。如果函数的值未被分配给任何东西,则不进行分配,该值将丢失。

  • 弹出要执行的下一条指令的地址,并使CPU在该指令处恢复执行。

参考链接:堆栈和堆

这是一篇很棒的文章!我已经将其加入书签,可以和那些学习堆栈的人分享 :) - Jason Coco
3
标准C++中没有栈的概念。 - fredoverflow
4
我很好奇C++标准是否有支持这个语句的章节?据我所知,关于堆栈的信息通常是特定CPU架构的调用约定的一部分,并且不必在每个可用平台上都相同。 - MKroehnert

6

这真的取决于你要返回哪种类型的变量。如果你返回一个原始类型,它会被复制而不是按引用返回,因此该值会被复制到堆栈顶部(或更常见的情况是放置到寄存器中),以便调用函数可以获取它。如果你在堆上分配了一个对象或内存并返回指针,则它不会死亡,因为它位于堆上而不是堆栈上。然而,如果你在堆栈上分配了某些东西并将其返回,那将是很糟糕的。例如,以下任何一种情况都会非常糟糕:

int *myBadAddingFunction(int a, int b)
{
    int result;

    result = a + b;
    return &result; // this is very bad and the result is undefined
}

char *myOtherBadFunction()
{
    char myString[256];

    strcpy(myString, "This is my string!");
    return myString; // also allocated on the stack, also bad
}

我实际上返回的是一个字符串..它不完全是一个数组,但我在函数中这样声明它:string 函数名(参数...) { string 测试; <代码... 代码> return 字符串; } 而且它运行得很好 - AntonioCS
如果它是一个实际的字符串对象,那么它会很好,因为它被分配在堆上,而不是栈上。在上面的糟糕示例中,myString实际上并不是一个对象,只是一个标准的char数组。CMS的“堆栈文章”非常好,你应该阅读一下 :) - Jason Coco

4
仅为更多的内存模型导向解释:当调用函数时,会为该函数创建一个临时空间来放置其局部变量,称为。当函数(被调用者)返回其值时,它将返回值放入调用它的函数(调用者)的帧中,然后被调用者的帧就被销毁了。
“帧被销毁”这一点是为什么不能从函数中返回指向局部变量的指针或引用。指针实际上是一个内存位置,因此返回局部变量的内存位置(根据定义:帧内的变量)在帧被销毁后就不正确了。由于被调用者的帧在返回其值后立即被销毁,因此任何指向局部变量的指针或引用都会立即失效。

4

当您返回一个值时,会创建一个副本。局部变量的范围结束了,但是会生成一个副本并返回到调用函数中。例如:

int funcB() {
  int j = 12;
  return j;
}

void A() {
  int i;
  i = funcB();
}

将 j 的值(12)复制并返回给 i,以便 i 接收到值为 12 的数值。


如果返回值是一个数组怎么办?我需要在返回之前分配内存吗?因为数组保存了内存的第一个地址。 - TomSawyer
1
你可以动态分配一个数组(new / malloc)。你的动态分配将会给一个本地变量赋值一个指针,该指针的值将被返回。返回一个在本地声明的数组(int[] var; std::array othervar;)是错误的 - 当函数退出时,作用域将结束,你的数组将无效。 - Kieveli
谢谢,所以如果在这种情况下i是一个结构体对象,那么我将&i作为参数传递给funcB - funcB不返回任何内容,只是使用i结构体成员进行计算,我需要对i进行malloc吗?不需要,对吧?因为i的值仍然在void A()的范围内,所以我不用担心在funcB完成后它的生命周期结束。 - TomSawyer
没错 - 你的解决方案比让funcB() malloc并返回指针更好。如果你不在同一位置分配和释放内存,很容易出现内存泄漏问题。 - Kieveli

3
这取决于所返回项的类型。如果您通过值返回,将创建一个变量的新副本以返回给调用者。在这种情况下,您不需要担心对象的生命周期,但是您可能需要担心复制对象的成本(但请不要过早优化-正确性更加重要):
std::string someFunc( std::string& const s)
{
    return s + "copy";
}

如果函数返回一个引用,那么你需要小心你要返回什么,因为它的生命周期需要延长到函数的生命周期之外,而调用者不一定能够使用delete来删除这个对象,如果你使用new创建了它。
std::string& someFunc2( std::string const& s)
{
    return s + "reference to a copy";   // this is bad - the temp object created will 
                                        //  be destroyed after the expression the 
                                        //  function call is in finishes.
                                        //  Some, but not all, compilers will warn 
                                        //  about this.
}

当然,返回指针也需要考虑类似的生命周期问题。

2
本地变量被复制到返回值中。对于非平凡类,会调用复制构造函数。
如果您返回指向本地变量的指针或引用,您将遇到麻烦——就像您的直觉所提示的那样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接