我有以下的代码。
#include <iostream>
int * foo()
{
int a = 5;
return &a;
}
int main()
{
int* p = foo();
std::cout << *p;
*p = 8;
std::cout << *p;
}
而且代码只是运行而没有运行时异常!
输出为58
怎么可能?一个局部变量的内存不是在其函数外部无法访问吗?
我有以下的代码。
#include <iostream>
int * foo()
{
int a = 5;
return &a;
}
int main()
{
int* p = foo();
std::cout << *p;
*p = 8;
std::cout << *p;
}
而且代码只是运行而没有运行时异常!
输出为58
怎么可能?一个局部变量的内存不是在其函数外部无法访问吗?
正如Alex指出的,这种行为是未定义的。实际上,大多数编译器都会警告不要这样做,因为这是导致程序崩溃的简单方法。
作为您可能会遇到的奇怪行为的一个例子,可以尝试以下示例:
int *a()
{
int x = 5;
return &x;
}
void b( int *c )
{
int y = 29;
*c = 123;
cout << "y=" << y << endl;
}
int main()
{
b( a() );
return 0;
}
这会输出“y=123”,但你的结果可能因机器不同而异(真的!)。您的指针正在破坏其他不相关的局部变量。
你只是返回了一个内存地址。虽然这是允许的,但很可能是一种错误。
如果您尝试解引用该内存地址,则会导致未定义的行为。
int * ref () {
int tmp = 100;
return &tmp;
}
int main () {
int * a = ref();
// Up until this point there is defined results
// You can even print the address returned
// but yes probably a bug
cout << *a << endl;//Undefined results
}
cout
之前存在一个问题。 *a
指向未分配(释放)的内存。即使您不对其进行解引用,它仍然是危险的(并且很可能是虚假的)。 - ereOn这是典型的未定义行为,在这里不到两天前就有讨论了——在网站上搜索一下。简而言之,你很幸运,但任何事情都可能发生,你的代码正在对内存进行无效访问。
你实际上触发了未定义行为。
返回临时变量的地址是可行的,但由于临时变量在函数结束时被销毁,访问这些变量的结果将是未定义的。
因此,你并没有修改 a
变量,而是修改了 a
所在内存位置。这种差异与崩溃和不崩溃之间的差异非常相似。
这是可能的,因为a
是一个在其作用域内(foo
函数)分配了临时内存的变量。当你从foo
返回后,该内存就可以被释放并被覆盖。
你正在进行的操作被描述为未定义行为,结果无法预测。
在典型的编译器实现中,您可以认为代码是“打印出地址为a的内存块的值,该地址曾经被占用”。此外,如果将一个新的函数调用添加到包含本地int
的函数中,a
的值(或a
曾经指向的内存地址)很可能会发生改变。这是因为堆栈将被覆盖以包含不同的数据帧。
然而,这是未定义的行为,您不应该依赖它来工作!
a
占用时,指针保存了a
的地址。虽然标准不要求实现定义目标生命周期结束后地址的行为,但它也认识到在某些平台上,UB以环境特征的记录方式进行处理。虽然局部变量的地址在超出其作用域后通常没有多大用处,但其他类型的地址在其各自目标的生命周期结束后仍可能具有意义。 - supercatrealloc
的指针与返回值进行比较,也不允许调整指向旧块内地址的指针以指向新块,但某些实现确实这样做,利用此功能的代码可能比必须避免涉及分配给 realloc
的指针的任何操作 - 甚至是比较 - 更有效率。 - supercat如果你使用::printf
而不是cout
,那么正确的控制台输出会发生很大变化。
你可以在下面的代码中尝试使用调试器进行测试(已在x86、32位、Visual Studio上测试通过):
char* foo()
{
char buf[10];
::strcpy(buf, "TEST");
return buf;
}
int main()
{
char* s = foo(); // Place breakpoint and the check 's' variable here
::printf("%s\n", s);
}
”
。 - Peter Mortensen函数返回后,所有标识符都被销毁,而不是保留在内存中的值。如果没有标识符,我们无法定位这些值。但该位置仍然包含之前函数存储的值。
因此,在此函数中,foo()
返回 a
的地址,而 a
在返回其地址后被销毁。您可以通过返回的地址访问修改后的值。
让我举个现实生活中的例子:
假设一个人将钱藏在某个地方,并告诉您这个地方。一段时间后,告诉您钱的位置的人去世了。但您仍然可以访问那笔隐藏的钱。
你的代码非常危险。你正在创建一个本地变量(在函数结束后被认为已经销毁),并且在它被销毁后返回该变量的内存地址。
这意味着内存地址可能有效也可能无效,你的代码将容易受到可能的内存地址问题(例如分段错误)的影响。
这意味着你正在做一件非常糟糕的事情,因为你正在传递一个不可信的指针给一个内存地址。
相反,考虑以下示例并进行测试:
int * foo()
{
int *x = new int;
*x = 5;
return x;
}
int main()
{
int* p = foo();
std::cout << *p << "\n"; // Better to put a newline in the output, IMO
*p = 8;
std::cout << *p;
delete p;
return 0;
}
new
。你教他们使用new
。但是你不应该使用new
。 - Lightness Races in Orbitnew
更好,我同意你的看法。但是对于初学者来说,使用和理解new
要比智能指针简单。我认为应该逐步增加复杂度。首先,必须了解什么是分配,然后才能达到下一步(可能是...如何更好地使用分配? -> 智能指针和std中的其他工具)。然而,我承认我是一个非常老派的C++自学者:D 所以 -> 我的错 :P - Nobunnew
和 delete
是一种高级话题,可以稍后教授。 :) - Lightness Races in Orbit