我想了解以下两个C程序之间的区别。
第一个程序:
void main()
{
int *a;
{
int b = 10;
a=&b;
}
printf("%d\n", *a);
}
第二个程序:
void main()
{
int *a;
a = foo();
printf("%d\n", *a);
}
int* foo()
{
int b = 10;
return &b;
}
在这两种情况下,局部变量(b
)的地址都会返回并分配给a
。我知道当a
超出范围时,不应访问内存b
指向的内存。但是,在编译上述两个程序时,我仅收到第二个程序的以下警告:
警告C4172:返回本地变量的地址或临时
为什么第一个程序没有得到类似的警告?
答案 0 :(得分:4)
正如您已经知道b
在每个实例中超出范围,并且访问该内存是非法的,我只是倾向于我的想法为什么只有一个案例抛出警告而其他案例没有。
在第二种情况下,您将返回存储在堆栈内存中的变量的地址。因此,编译器会检测到该问题并向您发出警告。
然而,第一种情况是跳过编译器检查,因为编译器发现有效的初始化地址被分配给a
。在许多情况下,编译器依赖于编码员的智慧。
描述你的第一个案例的类似例子可能是,
char temp[3] ;
strcpy( temp, "abc" ) ;
编译器发现temp
有一个内存空间,但它取决于编码器智能的数量char
,它们将在该内存区域中复制。
答案 1 :(得分:2)
你的foo()
函数有未定义的行为,因为它返回一个指针,指向不再使用的堆栈内存的一部分,并且很快就会在下一个函数调用时被覆盖
它被称为" b已超出范围"。 当然记忆仍然存在,目前可能没有改变,但这不能保证。
这同样适用于您的第一个代码,因为b
的范围也以声明b
的块的结束括号结束。
编辑:
您没有在第一个代码中收到警告,因为您没有返回任何内容。警告明确提到return
。并且由于编译器可以立即分配完整函数的堆栈空间并且包括所有子块,因此可以保证该值不会被覆盖。但它仍然是未定义的行为。
可能会收到其他警告。
答案 2 :(得分:0)
在第一个代码段中,即使您明确添加了括号,您使用的堆栈空间也在同一个区域中;代码中没有跳转或返回,因此代码仍然使用堆栈中的连续内存地址。有几件事情发生了:
对于第二个代码片段,函数调用表示跳转和返回,表示:
因为堆栈指针已经恢复,堆栈上的任何东西都不会丢失(但是),但堆栈上的任何操作都可能会覆盖这些值。
我认为很容易理解为什么你只在一个案例中得到警告以及预期的行为是什么......
答案 3 :(得分:0)
也许它与编译器的实现有关。在第二个程序中,编译器可以识别返回调用是一个警告,因为程序返回一个超出范围的变量。我认为使用ebp
注册信息很容易识别。但是在第一个程序中,我们的编译器需要做更多的工作来实现它。
答案 4 :(得分:0)
您的两个程序都会调用未定义的行为。在花括号内组合在一起的语句称为块或复合语句。块中定义的任何变量仅在该块中具有范围。一旦你走出块范围,该变量就不再存在,访问它是非法的。
int main(void) {
int *a;
{ // block scope starts
int b = 10; // b exists in this block only
a = &b;
} // block scope ends
// *a dereferences memory which is no longer in scope
// this invokes undefined behaviour
printf("%d\n", *a);
}
同样,函数中定义的自动变量具有函数范围。函数返回后,将无法再访问堆栈上分配的变量。这解释了第二个程序的警告。如果要从函数返回变量,则应动态分配它。
int main(void) {
int *a;
a = foo();
printf("%d\n", *a);
}
int *foo(void) {
int b = 10; // local variable
// returning the address of b which no longer exists
// after the function foo returns
return &b;
}
此外,main
的签名应为以下之一 -
int main(void);
int main(int argc, char *argv[]);
答案 5 :(得分:0)
在你的第一个节目中 -
变量b是块级变量,可见性在该块内 只要。 但是b的生命周期是函数的生命周期,所以它一直存在于主函数的退出。 由于b仍然被分配空间,* a打印存储在b中的值,因为点b。