为什么仅在使用 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.
见 godbolt。 printf
的整数参数进入 esi
,它来自被归零的 ebp
。我猜想有一个错过的优化,因为它在 printf
的调用中保存了相同的 0 ebp
以重新加载到 esi
,而不是之后重新归零 esi
。但是整个分析有点毫无意义,因为行为是未定义的。
Boo
本身被优化为一个函数,它只是 returns 一个 NULL
指针而不做任何其他事情,由于未定义的行为,这也是合法的,但是 [= 的那个版本10=] 没有被程序调用。 Foo
也被优化成一个函数 returns 立即不递归;编译器可以判断递归调用无效。 (如果将负参数传递给 Foo
,由于有符号整数溢出,您将有未定义的行为,因此编译器不需要处理这种情况。)
我试图理解 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.
见 godbolt。 printf
的整数参数进入 esi
,它来自被归零的 ebp
。我猜想有一个错过的优化,因为它在 printf
的调用中保存了相同的 0 ebp
以重新加载到 esi
,而不是之后重新归零 esi
。但是整个分析有点毫无意义,因为行为是未定义的。
Boo
本身被优化为一个函数,它只是 returns 一个 NULL
指针而不做任何其他事情,由于未定义的行为,这也是合法的,但是 [= 的那个版本10=] 没有被程序调用。 Foo
也被优化成一个函数 returns 立即不递归;编译器可以判断递归调用无效。 (如果将负参数传递给 Foo
,由于有符号整数溢出,您将有未定义的行为,因此编译器不需要处理这种情况。)