为什么仅在使用 gcc 编译器优化时错误地返回指向堆栈上值的指针时 printf 打印 0?

Why does printf print 0s when I wrongly returned pointer to value on the stack only when optimizing with gcc compiler?

我试图理解 printf 在这个例子中的行为。当然,这里的主要问题是我们正在返回一个指针,该指针指向堆栈上在函数 Boo 返回后弹出的值。

我是用gcc编译的。在 test1 中:我打印了两次 7,这是预期的。以及 test2 中第二个 printf 的垃圾值。但是当我用 gcc -O3 编译时,我在两种情况下都打印了 0 和一个关于返回局部变量地址的编译器警告。

test.c: In function ‘Foo’: test.c:8:12: warning: function returns address of local variable [-Wreturn-local-addr] return t; ^ test.c:5:9: note: declared here int j;

谁能帮我解释一下 printf 的行为是如何导致这种行为的?

int *Boo(int i, int *p)
{
    int j;
    int *t = &j;
    *t = i + *p;
    return t;
}

void Foo(int x)
{
    if (x == 0) { return;}
    Foo(x - 1);
}

//test1
int main(void)
{
    int x = 5;
    int *t = Boo(2, &x);
    printf("%d", *t);
    printf("%d", *t);

    return 0;
}

//test2
int main(void)
{
    int x = 5;
    int *t = Boo(2, &x);
    
    printf("%d", *t);
    Foo(8);
    printf("%d", *t);

    return 0;
}

GCC 认为 Boo returns 是指向局部变量的指针,因此任何使用该指针的尝试都是未定义的行为。这意味着,根据 C 标准,编译器可以做任何它想做的事情,在这种情况下,GCC 通常会生成“高效”的代码,即使它与程序员的意图完全无关。它内联了对 Boo 的调用,它没有可见的效果,因此被优化掉了,它选择了一种“有效”的方式来为 printf 提供一个参数,它恰好是常量 0.

godboltprintf 的整数参数进入 esi,它来自被归零的 ebp。我猜想有一个错过的优化,因为它在 printf 的调用中保存了相同的 0 ebp 以重新加载到 esi,而不是之后重新归零 esi。但是整个分析有点毫无意义,因为行为是未定义的。

Boo 本身被优化为一个函数,它只是 returns 一个 NULL 指针而不做任何其他事情,由于未定义的行为,这也是合法的,但是 [= 的那个版本10=] 没有被程序调用。 Foo 也被优化成一个函数 returns 立即不递归;编译器可以判断递归调用无效。 (如果将负参数传递给 Foo,由于有符号整数溢出,您将有未定义的行为,因此编译器不需要处理这种情况。)