当我尝试理解函数调用时,我编写了一个简单的代码。但我无法理解它的输出。
#include <stdio.h>
int* foo(int n)
{
int *p = &n;
return p;
}
int f(int m)
{
int n = 1;
return 999;
}
int main(int argc, char *argv[])
{
int num = 1;
int *p = foo(num);
int q = f(999);
printf("[%d]\n[%d]\n", *p, q);
/* printf("[%d]\n", *q); */
}
输出:
[999]
[999]
为什么*p
是999?
然后我修改了我的代码,如下所示:
#include <stdio.h>
int* foo(int n)
{
int *p = &n;
return p;
}
int f()
{
int n = 1;
return 999;
}
int main(int argc, char *argv[])
{
int num = 1;
int *p = foo(num);
int q = f();
printf("[%d]\n[%d]\n", *p, q);
/* printf("[%d]\n", *q); */
}
输出:
[1]
[999]
为什么*p
是1?我在Linux中,使用gcc但是Clang得到了相同的输出。
答案 0 :(得分:4)
除了你的代码提出了未定义的行为,因为你正在返回一个指向堆栈变量的指针这一事实,你要求通过改变f()的签名来改变行为的原因。
原因
原因在于编译器为函数构建堆栈帧的方式。假设编译器正在为foo()构建堆栈帧,如下所示:
Address Contents
0x199 local variable p
0x200 Saved register A that gets overwritten in this function
0x201 parameter n
0x202 return value
0x203 return address
对于f(int m),堆栈看起来很安静:
Address Contents
0x199 local variable n
0x200 Saved register A that gets overwritten in this function
0x201 parameter m
0x202 return value
0x203 return address
现在,如果在foo中返回指向'n'的指针会发生什么?结果指针将为0x201。返回foo后,堆栈顶部位于0x204。内存保持不变,您仍然可以读取值“1”。这有效直到调用另一个函数(在你的情况下为'f')。调用f后,位置0x201将被参数m的值覆盖。
如果您访问此位置(并使用printf语句),则会显示“999”。如果在调用f()之前复制了此位置的值,则会找到值“1”。
坚持我们的例子,f()的堆栈框架看起来像这样,因为没有指定参数:
Address Contents
0x200 local variable n
0x201 Saved register A that gets overwritten in this function
0x202 return value
0x203 return address
当您使用'1'初始化局部变量时,您可以在调用f()后在位置0x200处读取'1'。如果您现在从位置0x201读取值,您将获得已保存寄存器的内容。
其他一些陈述
勇敢地试图通过实验证明这种解释
首先,重要的是要看到上面的解释不依赖于真正的堆栈框架布局。我刚刚介绍了布局,以便使插图易于理解。
如果你想在你自己的机器上测试行为,我建议你带上你最喜欢的调试器,看一下局部变量和参数放置的地址,看看到底发生了什么。请记住:更改f的签名会更改堆栈中的信息。所以唯一真正的“便携式”测试是改变f()的参数并观察值p指向的输出。
在调用f(void)的情况下,放在堆栈上的信息大不相同,并且在位置p处写入的值不再依赖于参数或本地。它还可以依赖于main函数的堆栈变量。
在我的机器上,例如,再现显示您在第二个变体中读取的'1'来自保存用于存储'1'到“num”的寄存器,因为它似乎用于加载n。< / p>
我希望这会给你一些见解。如果您有其他问题,请发表评论。 (我知道这有点奇怪)
答案 1 :(得分:2)
您正在调用未定义的行为。您不能返回局部变量的地址(在本例中为参数int n
),并期望它稍后有用。
答案 2 :(得分:2)
一个局部变量,例如代码中的n
:
int* foo(int n)
{
int *p = &n;
return p;
}
{p <1}}函数完成后,“消失”。
您无法使用它,因为访问该变量可能会给您带来不可预测的结果。不过你可以写这样的东西:
foo
因为您的变量int* foo(int* n)
{
*n = 999;
return p;
}
int main(int argc, char *argv[])
{
int num = 1;
int *p = foo(&num);
printf("[%d]\n", *p);
}
在打印时仍然存在。
答案 3 :(得分:0)
在你的第一个样本中,当你做
时int num = 1;
int *p = foo(num);
其中foo()
是
int* foo(int n)
{
int *p = &n;
return p;
}
当传递来自num
的变量main()
时,它会按值传递给foo
。换句话说,在堆栈上创建了一个名为num
的变量n
的副本。 num
和n
都具有相同的值,但它们是不同的变量,因此会有不同的地址。
当您从p
返回foo()
时,main()
获取的地址值与num
<{1}}中的main()
地址不同/ p>
同样的解释适用于您修改的程序。
让我们看另一个例子来澄清:
int i = 2;
int * foo()
{
return &i;
}
int main() {
i = 1;
int *p = foo();
return 0;
}
在这种情况下,i
在堆上声明,同一i
在main()
和foo()
中都被引用。相同的地址和相同的值。
让我们看看第三个例子:
int i = 2;
int * foo(int i)
{
return &i;
}
int main() {
int i = 1;
int *p = foo(i);
return 0;
}
此处,即使存在全局i
,它也会被i
中的局部变量main()
隐藏,这就是传递给foo()
的内容。因此,从&i
返回的foo
,即p
中main()
的值,将与main()中声明的变量i的地址不同。
希望这澄清了变量范围和值传递,
答案 4 :(得分:0)
没有汇编程序输出并不容易,但这是我的猜测:
当地人和参数在筹码上被收录。因此,当调用foo
时,它将返回堆栈中第一个参数的地址。
在第一个示例中,您将一个参数传递给第二个函数,该函数也将被推送到堆栈中,正好是p
指向的位置。因此它会覆盖*p
。
在第二个示例中,第二个调用中未触及堆栈。旧值(num
)仍在那里。
答案 5 :(得分:0)
这种未定义的行为是由于堆栈的参与
int *p = foo(num);
int q = f(999);
在第一种情况下,当您说&num
时,它实际上将地址存储在存储num
的堆栈中。然后foo(num)完成它的执行,f(999)开始执行参数999.由于使用相同的堆栈,堆栈中存储num的相同位置现在具有参数999.并且我们知道堆栈是连续的
这是打印999
的原因。实际上两者都试图打印堆栈中相同位置的内容。
而在第二种情况下,num不会被覆盖,因为没有参数传递给f() 因此,这会按预期打印。