函数局部变量的返回工作原理是什么?

4

我对本地变量的 return ,以及使用指针返回地址和变量地址感到困惑。

首先:

#include <stdio.h>

int returner(void);

int main(void)
{
    printf("The value I got is = %d\n", returner());
    return 0;
}

int returner(void)
{
    int a = 10;
    return a;
}

输出:

The value I got is = 10

即使函数返回后,局部变量应该被释放,但它仍然被返回了。这是怎么回事?

第二点:

#include <stdio.h>

int *returner(void);

int main(void)
{
    int *pointer = returner();
    printf("The value I got is = %d\n", *pointer);
    return 0;
}

int *returner(void)
{
    int a = 10;
    return &a;
}

输出:

Test.c: In function 'returner':
Test.c:15:12: warning: function returns address of local variable [-Wreturn-local-addr]
   15 |     return &a;

为什么地址没有返回,即使像第一个示例中返回值一样? 第三条:
#include <stdio.h>

int *returner(void);

int main(void)
{
    int *pointer = returner();
    printf("The value I got is = %d\n", *pointer);
    return 0;
}

int *returner(void)
{
    int a = 10;
    int *ptr = &a;
    return ptr;
}

输出:

The value I got is = 10

现在,这个方法是如何返回局部变量的地址并打印它的正确值的,即使变量应该在函数返回后超出范围/被销毁?
请解释一下这三种情况下方法是如何工作的。

int a -> static int a - alex01011
4
使用static需要完全理解它的作用。我不会在上下文中轻易提出这样的建议。 - Eugene Sh.
这并不是核物理学。只需要查看文档,他就可以准确地理解它的作用。 - alex01011
1
这是未定义行为。C语言在变量超出作用域后不会清除其值。 - klutt
1
@stark,这实际上是一个问题。根据http://port70.net/~nsz/c/c11/n1570.html#6.2.4的规定:当指针所指向的对象(或刚好超过其生命周期)到达其生命周期的末尾时,指针的值变得不确定。 - Eugene Sh.
显示剩余3条评论
4个回答

3

在C语言中,返回值是按值传递的。

第一段代码返回变量a的值,即10,这是可以的,因为它只是本地变量a的一个副本。

第二段代码返回指向a的指针的值,即地址。很巧合地,在函数结束后该地址将无效,本地变量存储在该地址中的生命周期也随之结束。编译器足够聪明,能够检测到这一点并发出警告。

第三段代码不同,它将来自另一个指针的地址赋值给了一个指针,编译器没有进一步检查分配的地址是否有效,这个额外的间接层使编译器混淆了。

输出仍然是10,但这只是偶然的结果,这是未定义行为,因此,这是可能的结果之一。对于这种类型的结构,没有标准化的行为,并且我稍微调整了一下以演示它。正如您在这里所看到的 https://godbolt.org/z/eKerdM,启用了-O3优化。

使用clang编译器,程序会输出一些随机值:

The value I got is = -1313555320

没有任何警告产生。

gcc 进一步验证并检测到一个有问题的赋值:

<source>: In function 'returner':
<source>:16:12: warning: function returns address of local variable 
[-Wreturn-local-addr]
  16 |     return ptr;
     |            ^~~
<source>:14:9: note: declared here
  14 |     int a = 10;
     |         ^

程序输出 0:

The value I got is = 0
在这两种情况下,值不再是正确的,并且clang的行为与gcc不同。

1
int returner(void)
{
    int a = 10;
    return a;
}

上述函数通过值返回一个整数,就返回而言,它相当于写成了return 10;
int *returner(void)
{
    int a = 10;
    return &a;
}

以上是所谓的“未定义行为”的示例 - 它可能有时能够工作,但不稳定。返回的地址是指函数调用栈上的位置,用于提供存储给函数局部变量。在returner返回后,该内存不再保留用于保存int a,尽管它可能没有被重复使用,并且可能仍然具有该地址处的值10,但在此之后一段时间内不能保证。

第三个示例与第二个示例并没有太大区别。行为可能与第二个示例不同,因为栈分配不同,但都不应期望能够始终正常工作或可移植。


1
  • 首先输出:返回没有作用域的值(value)。
  • 其次输出:你正在尝试返回一个超出作用域的东西的地址。
  • 第三个输出:虽然你正试图做与之前相同的事情,但编译器没有检测到问题。然而,被返回的指针仍然存在问题。

0
函数返回的是变量 a,就像传递给函数的参数一样是它们的
唯一需要担心超出作用域的时候是当你返回指向具有自动存储期的局部函数变量的指针,例如:
int *badFunction ()
{
   int ret[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
   return ret;
}

在这种情况下,返回的值是指向数组ret的指针,但是,由于badFunction退出时ret超出了作用域,因此返回的指针显然无效。解决方法是使用具有static存储期限或堆的变量。
在您的情况下,使用普通的int,您不需要担心作用域问题。

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