返回指向本地变量的指针

8

我不知道为什么这个代码能够运行。因为x是一个局部变量,所以我原本认为在尝试返回它的值时会出现错误。然而,第一个printf语句正常输出了变量的值,但是接下来就只会输出0。有没有人能够解释一下这到底是怎么回事呢?

#include <stdio.h>

int* func1() {
    int x = 123123;
    int *y = &x;
    return y;
}

int main()
{
    int* c = func1();
    printf("%d\n", *c); // output: 123123
    printf("%d\n", *c); // output: 0
    return 0;
}

2
什么都没有发生在使用本地变量的空间中。然后,某些事情发生了。 - user253751
5
行为未定义。您正在引用不可用于您的存储堆栈上的内容。在其他任何操作它之前,它可能仍然保存来自先前函数调用的值,但您不能指望它这样做。显然,您对printf的调用在某个时候覆盖了该存储空间。这是一个错误。只需修复它并继续生活。 - Tom Karzes
c的赋值是未定义行为。 - chux - Reinstate Monica
这是一个好问题。问题似乎出在变量的作用域上。不要将局部变量地址返回给其他函数,因为局部变量的作用域有限,一旦函数调用完成,它将被销毁或分配给其他人。 - Kanji Viroja
问题写得很好,但是拜托,只要在谷歌上搜索这个问题的标题,就会出现至少3个完全相同的副本... - Matteo Italia
显示剩余3条评论
3个回答

5
以下发生了什么:
  1. func1函数内,您创建了本地变量x并将其初始化为一个值,即x现在在堆栈上。
  2. 您获取x的地址并将其返回给main
  3. 在返回时,func1和其变量x(以及与问题无关的y)被释放或从堆栈中弹出,即它们的内存位置不再保留以保存它们的值。在此之后,程序的任何其他部分都可以使用为func1分配的内存空间,因为func1不再处于活动状态。
  4. 您的第一个printf调用仍然会看到x曾经存在的内存位置的旧值(但这并不保证),
  5. 第二个printf调用表明其他东西(例如第一个printf的返回值,如 R. Joiny所描述)正在使用func1x相同的地址。

这篇C编程营地文章基本上描述了你的情况:

理解堆栈的关键是,当函数退出时,所有变量都从堆栈中弹出(因此永远丢失)。因此,堆栈变量具有局部性质。这与我们早先看到的变量作用域或局部和全局变量的概念有关。 C编程中常见的错误是尝试从程序外部某个位置(即在该函数退出后)访问在堆栈上创建的变量。


5

printf是一个函数,就像你的func1一样。

函数使用一些RAM(随机存储器),作为本地变量的工作区。如果你离开函数,它会被分配为“可用清空”(而不是删除!)。

因为第一个printf()直接在func1()函数后面出现,所以本地变量C仍然存在,因为它尚未被覆盖。所以这可以正常工作。但是,如果你查看MAN页面,你会发现,printf有int作为返回值。所以必须将其写入某个地方,那么你的PC会怎么做?当然写到RAM的第一个自由地址,该地址已分配给你的程序。这就是你的零。

重要的是要注意,没有其他程序可以访问你的RAM,Windows自动为每个进程保留RAM,所以这必须是printf()的返回值(或者printf执行时使用的其他本地变量)。


5
由于x是一个局部变量,因此我认为当我尝试返回它时会出现错误。
对于这个问题的更一般的答案是:在C代码中错误的代码不一定会产生编译器或运行时错误。
如果你非常幸运,你会得到一个编译器错误或警告。
如果你幸运的话,在错误的代码运行时会崩溃(这经常发生在空指针错误中)。这很容易用调试器跟踪。
但如果你不幸的话,错误的代码可能会在以后导致崩溃(在看似无关的代码中),或默默地破坏一些数据结构(使你的程序表现出奇怪的方式而没有崩溃),甚至表现得很好(直到你在其他地方添加看似无害的代码,或使用不同的编译器,或同一编译器的不同版本,或不同的编译选项)。
从技术上讲,你的代码在这一行具有未定义的行为:
int* c = func1();

你正在使用func1的返回值(将其写入c中)。但该值是func1中的局部变量的地址,当func返回时,该变量已经不存在了。这意味着返回值是一个无效的指针,仅仅是使用这样的指针就会导致未定义的行为(你甚至不需要解引用它)。
printf("%d\n", *c); // output: 123123

这行代码既使用了错误的指针(从c读取它),又对其进行了解引用。但程序似乎仍然正常工作。

printf("%d\n", *c); // output: 0

这似乎是一个无害的添加,但这一行并没有产生预期的输出。现在看起来像是静默数据损坏。

请注意,这些都不是保证的。使用不同优化设置的不同编译器或相同编译器可能会产生行为不同的代码。就标准而言,编译器必须生成与C代码的可观察行为匹配的代码。但是,行为未定义的C代码可能会导致任何事情发生;对于编译器可以对其进行什么操作没有任何保证或限制。


这真的是一个编译器的问题,而不是更可能是操作系统的问题,会发生什么?我对编译器了解不多,但我认为操作系统通过在RAM上分配所需的内存来给指针赋值。 - R. Joiny

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