can';我不理解linux中有关函数调用的简单c代码的输出
当我试图理解函数调用时,我编写了一个简单的代码。但我不能理解它的输出can';我不理解linux中有关函数调用的简单c代码的输出,c,linux,gcc,compiler-construction,C,Linux,Gcc,Compiler Construction,当我试图理解函数调用时,我编写了一个简单的代码。但我不能理解它的输出 #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
#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); */
}
为什么*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); */
}
为什么
*p
这里是1?我在Linux中使用gcc,但Clang得到了相同的输出。您正在调用未定义的行为。您不能返回局部变量的地址(在本例中为参数int n
),并期望它以后有用。局部变量,如代码中的n
:
int* foo(int n)
{
int *p = &n;
return p;
}
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);
}
因为您的变量
num
在打印时仍然存在。在您的第一个示例中,当您这样做时
int num = 1;
int *p = foo(num);
其中foo()
是
int* foo(int n)
{
int *p = &n;
return p;
}
当从main()
传递variabenum
时,它将通过值传递给foo
。换句话说,将在堆栈上创建名为n
的变量num
的副本。num
和n
都具有相同的值,但它们是不同的变量,因此具有不同的地址
当您从foo()
返回p
时,main()
获取与main()中的num
delared地址不同的地址值
同样的解释也适用于修改后的程序
让我们看另一个例子来澄清:
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
,它也被main()
中的局部变量i
隐藏,这就是传递给foo()
的内容。因此,从foo
返回的&i
,即main()
中p
的值将不同于我在main()中声明的变量的地址
希望这能澄清变量范围和按值传递的问题,没有汇编器输出并不容易,但这是我的猜测:
局部变量和参数都在堆栈上。因此,当调用foo
时,它将返回堆栈上第一个参数的地址
在第一个示例中,您将一个参数传递给第二个函数,该函数也将被推送到堆栈上,p
恰好指向的位置。因此,它将覆盖*p
的值
在第二个示例中,在第二个调用中未触及堆栈。旧值(num
)仍保留在那里。除了您的代码由于返回堆栈变量的指针而引发未定义行为这一事实之外,您还询问了为什么行为会随着f()签名的更改而改变
原因是什么
原因在于编译器为函数构建stackframe的方式。假设编译器正在为foo()构建堆栈框架,如下所示:
对于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()的stackframe如下所示,因为没有指定任何参数:
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读取该值,则将获得已保存寄存器的内容
一些进一步的声明
- 理解上面的解释是向你展示为什么你观察你所观察的东西的方法,这一点至关重要
- 真正的行为取决于您使用的工具链和所谓的调用转换
- 人们可以很容易地想象,有时很难预测会发生什么。这与释放内存后访问内存的情况非常相似。这就是为什么发生的事情通常是不可预测的李>
- 这种行为甚至可以随着优化级别的改变而改变。例如,我可以想象,如果你打开-O3,观察结果将不同,因为未使用的变量n将不再出现在二进制文件中
- 理解了背后的机制之后,应该可以理解为什么对从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
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
Address Contents
0x200 local variable n
0x201 Saved register A that gets overwritten in this function
0x202 return value
0x203 return address
int *p = foo(num);
int q = f(999);