是否可以在其范围之外访问局部变量的内存?

Can a local variable's memory be accessed outside its scope?

提问人: 提问时间:6/22/2011 最后编辑:24 revs, 14 users 26%unknown 更新时间:9/16/2023 访问量:302105

问:

我有以下代码。

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

而且代码只是在运行,没有运行时异常!

输出为58

怎么可能?局部变量的内存不是在其函数之外无法访问的吗?

C++ 内存管理 局部变量 悬空指针

评论

15赞 sehe 6/22/2011
这甚至不会按原样编译;如果你修复了非成型业务,GCC仍然会发出警告;Valgrind 表演address of local variable ‘a’ returnedInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
89赞 Eric Lippert 6/23/2011
@Serge:在我年轻的时候,我曾经研究过一些在Netware操作系统上运行的棘手的零环代码,它涉及以一种操作系统并不完全认可的方式巧妙地在堆栈指针周围移动。我知道我什么时候犯了错误,因为堆栈通常最终会与屏幕内存重叠,而我只能看着字节直接写入显示器。这些天你无法逃脱这种事情。
23赞 erikbstack 6/23/2011
哈哈。在我了解问题所在之前,我需要阅读问题和一些答案。这实际上是关于变量访问范围的问题吗?您甚至不会在函数之外使用“a”。这就是它的全部内容。抛出一些内存引用与可变范围是完全不同的主题。
12赞 Joel Spolsky 6/23/2011
欺骗答案并不意味着欺骗问题。人们在这里提出的许多重复问题都是完全不同的问题,恰好指的是相同的潜在症状......但提问者知道这一点,所以他们应该保持开放。我关闭了一个较旧的骗局并将其合并到这个问题中,该问题应该保持开放状态,因为它有一个非常好的答案。
16赞 sbi 6/23/2011
@Joel:如果这里的答案是好的,它应该合并到较旧的问题中,这是一个骗局,而不是相反。这个问题确实是这里提出的其他问题的骗局,然后是一些问题(尽管其中一些问题比其他问题更适合)。请注意,我认为埃里克的回答很好。(事实上,我标记了这个问题,因为将答案合并到一个较旧的问题中,以挽救较旧的问题。

答:

160赞 msw 5/19/2010 #1

因为存储空间还没有被踩踏。不要指望这种行为。

评论

2赞 Rob Kent 7/17/2015
伙计,这是自“真理是什么?开玩笑说彼拉多。也许是酒店抽屉里的一本基甸圣经。他们到底发生了什么?请注意,至少在伦敦,它们不再存在。我猜想,根据平等立法,你需要一个宗教小册子图书馆。
0赞 msw 7/17/2015
我本可以发誓我很久以前就写过了,但它最近突然出现,发现我的回应不存在。现在我必须去弄清楚你上面的典故,因为我希望当我这样做时我会被逗乐>.<
2赞 Rob Kent 7/17/2015
哈哈。弗朗西斯·培根(Francis Bacon)是英国最伟大的散文家之一,有人怀疑他写了莎士比亚的戏剧,因为他们无法接受一个来自乡村的文法学校的孩子,一个手套的儿子,可以是一个天才。这就是英语班级制度。耶稣说:“我是真理”。oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
18赞 Brian R. Bondy 5/19/2010 #2

您只是返回一个内存地址。这是允许的,但这可能是一个错误。

是的,如果您尝试取消引用该内存地址,您将有未定义的行为

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
}

评论

0赞 ereOn 5/19/2010
我不同意:在. 指向未分配(释放)的内存。即使你不拒绝它,它仍然是危险的(而且可能是假的)。cout*a
0赞 Brian R. Bondy 5/19/2010
@ereOn:我进一步澄清了我所说的问题的含义,但就有效的 c++ 代码而言,它并不危险。但就用户可能犯了错误并会做坏事而言,这是危险的。例如,也许您正在尝试查看堆栈如何增长,并且您只关心地址值,并且永远不会取消引用它。
29赞 gastush 6/22/2011 #3

您是否在启用优化器的情况下编译程序?该函数非常简单,可能已在生成的代码中内联或替换。foo()

但我同意 Mark B 的观点,即由此产生的行为是未定义的。

评论

0赞 Erik Aronesty 6/23/2011
这是我的赌注。优化程序转储了函数调用。
10赞 Tomas 6/23/2011
那没有必要。由于 foo() 之后没有调用新函数,因此函数本地堆栈帧尚未被覆盖。在 foo() 之后添加另一个函数调用,并且会更改...5
0赞 Kevin 5/4/2017
我使用 GCC 4.8 运行该程序,用 printf(包括 stdio)替换 cout。正确地警告“警告:返回了局部变量'a'的地址[-Wreturn-local-addr]”。输出 58 不带优化,输出 08 带 -O3。奇怪的是,P 确实有一个地址,即使它的值是 0。我希望 NULL (0) 作为地址。
71赞 Charles Brunet 6/22/2011 #4

在 C++ 中,您可以访问任何地址,但这并不意味着您应该访问。您正在访问的地址不再有效。它之所以有效,是因为在 foo 返回后没有其他东西扰乱内存,但它在许多情况下可能会崩溃。尝试使用 Valgrind 分析您的程序,甚至只是对其进行优化编译,然后查看...

评论

6赞 v010dya 5/12/2015
您可能意味着您可以尝试访问任何地址。因为今天的大多数操作系统都不会让任何程序访问任何地址;有大量的保护措施来保护地址空间。这就是为什么不会有另一个LOADLIN.EXE的原因。
68赞 Kerrek SB 6/22/2011 #5

您永远不会通过访问无效内存来引发 C++ 异常。您只是举了一个示例来说明引用任意内存位置的一般思路。我可以像这样做同样的事情:

unsigned int q = 123456;

*(double*)(q) = 1.2;

在这里,我只是将123456视为双精度的地址并写信给它。可能发生许多事情:

  1. q实际上可能真的是双精度的有效地址,例如 .double p; q = &p;
  2. q可能指向分配内存中的某个地方,我只是覆盖其中的 8 个字节。
  3. q点在分配的内存之外,操作系统的内存管理器会向我的程序发送分段故障信号,导致运行时终止它。
  4. 你中了彩票。

设置它的方式更合理一些,返回的地址指向一个有效的内存区域,因为它可能只是在堆栈的更下方,但它仍然是一个无效的位置,你不能以确定性的方式访问。

在正常程序执行期间,没有人会像这样自动检查内存地址的语义有效性。但是,像 Valgrind 这样的内存调试器会很乐意这样做,因此您应该通过它运行程序并见证错误。

评论

11赞 Aidiakapi 3/11/2015
我现在只想编写一个程序,继续运行这个程序,以便4) I win the lottery
0赞 Peter Mortensen 2/20/2023
恒定0xDEADBEEF更丰富(且不均匀),并保证在大多数系统上有一些可见的动作。
14赞 larsmoa 6/22/2011 #6

在典型的编译器实现中,您可以将代码视为“打印出内存块的值,其中包含过去被 a 占用的地址”。此外,如果将新的函数调用添加到包含本地的函数,则 的值(或过去指向的内存地址)很有可能发生变化。发生这种情况是因为堆栈将被包含不同数据的新帧覆盖。intaa

但是,这是未定义的行为,您不应依赖它来工作!

评论

4赞 Brennan Vincent 6/23/2011
“打印出曾经被 A 占用的地址的内存块的值”不太正确。这听起来像是他的代码具有一些明确定义的含义,但事实并非如此。不过,您说得对,这可能是大多数编译器实现它的方式。
0赞 supercat 6/27/2018
@BrennanVincent:当存储被 占用时,指针持有 的地址。尽管该标准不要求实现在其目标的生存期结束后定义地址的行为,但它也承认在某些平台上,UB 是以环境特征的记录方式处理的。虽然局部变量的地址在超出范围后通常没有多大用处,但其他一些类型的地址在其各自目标的生存期后可能仍然有意义。aa
0赞 supercat 6/27/2018
@BrennanVincent:例如,虽然标准可能不要求实现允许将传递到的指针与返回值进行比较,也不允许将指向旧块中地址的指针调整为指向新块,但某些实现会这样做,并且利用此类功能的代码可能比必须避免任何操作(甚至是比较)的代码更有效,这些操作涉及指向分配给的指针。reallocrealloc
5001赞 Eric Lippert 6/23/2011 #7

怎么可能?局部变量的内存不是在其函数之外无法访问的吗?

你租一个酒店房间。你把一本书放在床头柜的顶部抽屉里,然后去睡觉。你第二天早上退房,但“忘记”归还你的钥匙。你偷了钥匙!

一周后,你回到酒店,不办理入住手续,拿着偷来的钥匙偷偷溜进你的旧房间,在抽屉里看看。你的书还在那里。惊人!

这怎么可能?如果您没有租过房间,酒店房间抽屉里的东西不是无法取用吗?

嗯,显然这种情况可以在现实世界中发生,没有问题。当你不再被授权进入房间时,没有任何神秘的力量会导致你的书消失。也没有一种神秘的力量阻止你带着被盗的钥匙进入房间。

酒店管理层无需删除您的预订。你没有和他们签订合同,说如果你留下东西,他们会为你撕碎。如果您使用偷来的钥匙非法重新进入您的房间以取回它,酒店保安人员不需要抓住您偷偷溜进来。你没有和他们签订合同,上面写着“如果我以后试图偷偷溜回我的房间,你必须阻止我。相反,你和他们签订了一份合同,上面写着“我保证以后不会偷偷溜回我的房间”,但你打破了这份合同。

在这种情况下,任何事情都可能发生。这本书可以在那里——你很幸运。别人的书可能在那里,而你的书可能在酒店的炉子里。当你进来时,有人可能就在那里,把你的书撕成碎片。酒店本可以完全拆除桌子和预订,并用衣柜代替它。整个酒店可能即将被拆除,取而代之的是足球场,而你偷偷摸摸的时候会在爆炸中丧生。

你不知道会发生什么;当你退房并偷走了一把钥匙以供以后非法使用时,你放弃了生活在一个可预测的、安全的世界中的权利,因为你选择打破了系统的规则。

C++ 不是一种安全的语言。它会愉快地让你打破系统的规则。如果你试图做一些非法和愚蠢的事情,比如回到一个你没有被授权进入的房间,在一张甚至可能不再存在的桌子上翻找,C++ 不会阻止你。比 C++ 更安全的语言通过限制您的权力(例如,通过对密钥进行更严格的控制)来解决这个问题。

更新

天哪,这个答案引起了很多关注。(我不知道为什么——我认为这只是一个“有趣”的小类比,但无论如何。

我认为用更多的技术思想来更新一下可能会很有意义。

编译器的业务是生成代码,这些代码管理该程序操作的数据的存储。有许多不同的方法可以生成代码来管理内存,但随着时间的推移,两种基本技术已经根深蒂固。

第一种是建立某种“长期”存储区域,其中存储中每个字节的“生存期”(即它与某些程序变量有效关联的时间段)不能轻易提前预测。编译器生成对“堆管理器”的调用,该管理器知道如何在需要时动态分配存储,并在不再需要时回收存储。

第二种方法是有一个“短期”存储区域,其中每个字节的生存期是众所周知的。在这里,生存期遵循“嵌套”模式。这些短期变量中生存期最长的变量将在任何其他短期变量之前分配,并将最后释放。生存期较短的变量将在生存期最长的变量之后分配,并在它们之前释放。这些生存期较短的变量的生存期“嵌套”在生存期较长的变量的生存期内。

局部变量遵循后一种模式;当输入方法时,其局部变量将激活。当该方法调用另一个方法时,新方法的局部变量将激活。在第一种方法的局部变量失效之前,它们就已经死了。可以提前计算出与局部变量关联的存储生命周期的开始和结束的相对顺序。

出于这个原因,局部变量通常作为“堆栈”数据结构上的存储生成,因为堆栈具有这样的属性,即推送到它上面的第一件事将是最后弹出的东西。

这就像酒店决定只按顺序出租房间,直到房间号高于您的每个人都退房后,您才能退房。

因此,让我们考虑一下堆栈。在许多操作系统中,每个线程都有一个堆栈,并且堆栈被分配为一定的固定大小。当你调用一个方法时,内容会被推送到堆栈上。如果随后将指向堆栈的指针从方法中传回,就像此处的原始海报一样,则这只是指向某个完全有效的百万字节内存块中间的指针。在我们的类比中,您从酒店退房;当您这样做时,您刚刚从入住人数最多的房间退房。如果没有其他人在你身后办理入住手续,而你非法回到你的房间,你所有的东西都保证仍然在这家特定的酒店里。

我们使用堆栈作为临时商店,因为它们非常便宜且简单。使用堆栈存储局部变量不需要 C++ 的实现;它可以使用堆。它不会,因为这会使程序变慢。

不需要 C++ 的实现来保持您留在堆栈上的垃圾不变,以便您以后可以非法返回它;编译器生成的代码将您刚刚腾出的“房间”中的所有内容都归零是完全合法的。它没有,因为同样,那会很昂贵。

不需要 C++ 的实现来确保当堆栈在逻辑上收缩时,以前有效的地址仍映射到内存中。允许实现告诉操作系统“我们现在已经完成了堆栈的这一页。在我另有说明之前,请发出一个例外,如果有人触摸以前有效的堆栈页面,则会破坏该过程”。同样,实现实际上并没有这样做,因为它很慢且没有必要。

相反,实现会让你犯错误并逃脱惩罚。大多数时候。直到有一天,一些真正可怕的事情出了问题,这个过程爆炸了。

这是有问题的。有很多规则,很容易不小心打破它们。我当然有很多次。更糟糕的是,问题通常只有在损坏发生数十亿纳秒后检测到内存损坏时才会浮出水面,这时很难弄清楚是谁搞砸了它。

更多内存安全语言通过限制您的能力来解决这个问题。在“普通”C# 中,根本没有办法获取本地地址并返回或存储它以备后用。您可以采用本地地址,但该语言设计巧妙,因此在本地结束的生命周期之后无法使用它。为了获取本地地址并将其传回,您必须将编译器置于特殊的“不安全”模式,并在程序中加入“不安全”一词,以引起人们的注意,即您可能正在做一些可能违反规则的危险事情。

进一步阅读:

  • 如果 C# 允许返回引用呢?巧合的是,这就是今天博客文章的主题:

    Ref 返回和 ref 局部变量

  • 为什么我们使用堆栈来管理内存?C# 中的值类型是否始终存储在堆栈中?虚拟内存如何工作?以及有关 C# 内存管理器工作原理的更多主题。其中许多文章也与 C++ 程序员密切相关:

    内存管理

评论

61赞 Eric Lippert 6/23/2011
@muntoo:不幸的是,操作系统在取消提交或解除分配虚拟内存页面之前不会发出警告警报。如果你在不再拥有内存时还在乱搞它,那么当你触摸一个已释放的页面时,操作系统完全有权关闭整个过程。繁荣!
90赞 Alexander Torstling 6/23/2011
@Kyle:只有安全的酒店才能做到这一点。不安全的酒店不必浪费时间在编程键上,从而获得可衡量的利润收益。
530赞 Eric Lippert 6/23/2011
@cyberguijarro:C++不是内存安全的,这只是一个事实。它不是“抨击”任何东西。例如,如果我说,“C++ 是一个可怕的大杂烩,堆积在一个脆弱、危险的内存模型之上,我每天都很感激我不再为自己的理智而工作”,那将是对 C++ 的抨击。指出它不是内存安全的,这解释了为什么原始海报会看到这个问题;它是在回答问题,而不是社论。
64赞 philsquared 6/23/2011
严格来说,这个类比应该提到,酒店的接待员很高兴你带上钥匙。“哦,你介意我把这把钥匙带走吗?”“去吧。我为什么要关心?我只在这里工作”。在你尝试使用它之前,它不会变得非法。
154赞 Dyppl 6/24/2011
拜托,请至少考虑有一天写一本书。即使它只是经过修改和扩展的博客文章的集合,我也会购买它,我相信很多人也会购买。但是,一本包含您对各种编程相关问题的原始想法的书将是一本很棒的读物。我知道很难找到时间,但请考虑写一个。
24赞 Chang Peng 6/23/2011 #8

您的问题与范围无关。在你展示的代码中,函数看不到函数中的名称,所以你不能直接在foo中用这个名字在外面访问。mainfooafoo

您遇到的问题是为什么程序在引用非法内存时没有发出错误信号。这是因为 C++ 标准没有指定非法内存和合法内存之间的非常明确的界限。在弹出的堆栈中引用某些内容有时会导致错误,有时则不会导致错误。这要视情况而定。不要指望这种行为。假设它在编程时总是会导致错误,但假设它在调试时永远不会发出错误信号。

评论

1赞 user 6/23/2011
我记得在一本旧的 Turbo C Programming for the IBM 中,我曾经玩过它,当时非常详细地描述了如何直接操作图形内存,以及 IBM 文本模式视频内存的布局。当然,运行代码的系统清楚地定义了写入这些地址的含义,所以只要你不担心可移植到其他系统,一切都很好。IIRC,指向虚空的指针是该书的一个共同主题。
0赞 Chang Peng 6/23/2011
@Michael Kjörling:当然!人们喜欢偶尔做一些肮脏的工作;)
283赞 Rena 6/23/2011 #9

你只是在读取和写入曾经是 的地址的内存。现在你不在了,它只是指向某个随机内存区域的指针。碰巧的是,在您的示例中,该内存区域确实存在,目前没有其他区域在使用它。afoo

继续使用它不会破坏任何东西,而且还没有其他任何东西覆盖它。因此,仍然存在。在真正的程序中,该内存几乎会立即被重用,并且这样做会破坏某些东西(尽管症状可能要到很久以后才会出现!5

当您从 返回时,您告诉操作系统您不再使用该内存,并且可以将其重新分配给其他内存。如果你很幸运,它永远不会被重新分配,并且操作系统没有发现你再次使用它,那么你就会逃脱谎言。很有可能,你最终会写下任何其他以该地址结尾的东西。foo

现在,如果你想知道为什么编译器不抱怨,那可能是因为被优化淘汰了。它通常会警告你这种事情。C 假设你知道你在做什么,从技术上讲,你没有违反这里的范围(除了 之外没有引用它自己),只有内存访问规则,它只会触发警告而不是错误。fooafoo

简而言之:这通常行不通,但有时是偶然的。

20赞 Adrian Grigore 6/23/2011 #10

它之所以有效,是因为自从 a 放在那里以来,堆栈还没有被更改。 在再次访问之前调用一些其他函数(也在调用其他函数),您可能不再那么幸运了...... ;-)a

12赞 3 revs, 3 users 67%Mykola #11

如果您使用但不使用,具有正确 (?) 控制台输出的内容可能会发生巨大变化。::printfcout

可以在以下代码中使用调试器(在 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);
}

评论

0赞 Peter Mortensen 2/12/2023
这不会编译(也由语法突出显示指示)。有一个非 ASCII 双引号:
16赞 Alexander Gessler 6/25/2011 #12

您实际上调用了未定义的行为。

返回临时地址是有效的,但是由于临时地址在函数结束时被销毁,因此访问它们的结果将是不确定的。

所以你没有修改,而是修改了曾经的内存位置。这种差异与崩溃和不崩溃之间的区别非常相似。aa

14赞 littleadv 6/25/2011 #13

它可以,因为是在其作用域(函数)的生存期内临时分配的变量。从内存返回后,它是空闲的,可以被覆盖。afoofoo

您正在做的事情被描述为未定义的行为。结果无法预测。

17赞 Kerrek SB 6/25/2011 #14

这是两天前在这里讨论过的经典的未定义行为 - 在网站上搜索一下。简而言之,你很幸运,但任何事情都可能发生,你的代码对内存的访问无效。

18赞 AHelps 6/25/2011 #15

正如 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”,但您的结果可能会有所不同(真的!您的指针正在干扰其他不相关的局部变量。

95赞 3 revs, 2 users 76%Michael #16

对所有答案进行一些补充:

如果你做这样的事情:

#include <stdio.h>
#include <stdlib.h>

int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n", *p);
}

输出可能是:7

这是因为从 foo() 返回后,堆栈被释放,然后被 boo() 重用。

如果拆解可执行文件,您将清楚地看到它。

评论

3赞 proton 5/8/2013
简单但很好的例子来理解底层堆栈理论。只需添加一个测试,将 foo() 中的 “int a = 5;” 声明为 “static int a = 5;”,就可以用于了解静态变量的范围和生存期。
18赞 Matt 10/10/2013
-1 “因为可能是 7”。编译器可能会在 boo 中注册 a。它可能会删除它,因为它是不必要的。*p 很有可能不是 5,但这并不意味着它有什么特别好的理由可能是 7
2赞 Francis Cugler 3/27/2015
这就是所谓的未定义行为!
0赞 ampawd 8/22/2016
为什么以及如何重用堆栈?函数堆栈不是彼此分离的,我在 Visual Studio 2015 上运行此代码时也会遇到垃圾boofoo
1赞 Russ Schultz 7/16/2017
@ampawd它已经快一年了,但不,“函数堆栈”并不是彼此分离的。CONTEXT 具有堆栈。该上下文使用其堆栈进入 main,然后下降到 ,exists,然后下降到 。 并且两者都使用同一位置的堆栈指针进入。然而,这不是应该依赖的行为。其他“东西”(如中断或操作系统)可以在调用 和 之间使用堆栈,修改其内容......foo()boo()Foo()Boo()boo()foo()
21赞 3 revs, 3 users 50%Peter Mortensen #17

注意所有警告。不仅要解决错误。

GCC 显示以下警告

警告:返回局部变量“a”的地址

这就是 C++ 的强大功能。你应该关心记忆。使用 -Werror 标志时,此警告成为错误,现在您必须对其进行调试。

评论

0赞 John McFarlane 1/31/2022
这是最实际的答案。将默认编译器标志视为“兼容模式”。除非处理遗留代码,否则不要使用此模式。相反,请打开警告。( 是一个好的开始。此外,如果您不确定程序是否正确,请添加运行时检查,如下所示-Werror -Wall -Wextra-fsanitize=address,undefined
6赞 2 revs, 2 users 80%Peter Mortensen #18

这是使用内存地址的“肮脏”方式。当你返回一个地址(指针)时,你不知道它是否属于函数的本地范围。它只是一个地址。

现在您调用了“foo”函数,“a”的地址(内存位置)已经分配在应用程序(进程)的可寻址内存中(至少现在是安全的)。

在返回“foo”函数后,“a”的地址可以被认为是“脏的”,但它就在那里,没有被清理,也没有被程序其他部分的表达式干扰/修改(至少在这种特定情况下)。

C/C++ 编译器不会阻止你进行这种“肮脏”的访问(如果你在乎的话,它可能会警告你)。您可以安全地使用(更新)程序实例(进程)的数据段中的任何内存位置,除非您通过某种方式保护地址。

5赞 2 revs, 2 users 86%Ghulam Moinul Quadir #19

从函数返回后,所有标识符都会被销毁,而不是将值保存在内存位置中,并且如果没有标识符,我们就无法找到这些值。但该位置仍包含上一个函数存储的值。

因此,这里的函数返回 的地址,并在返回其地址后被销毁。您可以通过返回的地址访问修改后的值。foo()aa

让我举一个真实世界的例子:

假设一个人把钱藏在某个地方,并告诉你这个位置。过了一段时间,告诉你钱的位置的人死了。但你仍然可以获得这笔隐藏的钱。

0赞 2 revs, 2 users 80%Nobun #20

你的代码风险很大。您正在创建一个局部变量(在函数结束后被视为已销毁),并在该变量销毁后返回该变量的内存地址。

这意味着内存地址可能有效,也可能无效,并且您的代码将容易受到可能的内存地址问题(例如,分段错误)的影响。

这意味着你正在做一件非常糟糕的事情,因为你正在将一个内存地址传递给一个根本不可信的指针。

请改为考虑此示例并对其进行测试:

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;
}

与您的示例不同,在此示例中,您将:

  • int 的内存分配给本地函数
  • 当函数到期时,该内存地址仍然有效(任何人都不会删除它)
  • 内存地址是可信的(该内存块不被视为空闲,因此在删除之前不会被覆盖)
  • 不使用时应删除内存地址。(请参阅程序末尾的删除)

评论

0赞 Lightness Races in Orbit 5/2/2019
您是否添加了现有答案尚未涵盖的内容?请不要使用原始指针/。new
1赞 Nobun 5/2/2019
提问者使用了原始指针。我做了一个例子,准确地反映了他所做的例子,以便让他看到不可信指针和可信指针之间的区别。实际上还有另一个与我类似的答案,但它使用 strcpy wich,恕我直言,对于新手编码人员来说可能不如我使用 new 的例子清楚。
0赞 Lightness Races in Orbit 5/2/2019
他们没有使用 .你正在教他们使用 .但你不应该使用 .newnewnew
0赞 Nobun 5/2/2019
因此,在您看来,将地址传递给在函数中被销毁的局部变量比实际分配内存更好?这毫无意义。理解分配的概念很重要 e 解除分配内存,恕我直言,主要是如果你问指针(asker 没有使用新的,而是使用了指针)。
0赞 Lightness Races in Orbit 5/2/2019
我什么时候这么说的?否,最好使用智能指针来正确指示引用资源的所有权。不要在 2019 年使用(除非你正在编写库代码),也不要教新人这样做!干杯。new
0赞 JohnZ #21

快速回答:你在这里所做的被称为悬空指针。当你退出功能范围时,里面的一切都被破坏了,所以从技术上讲,你的指针只是无处可指。当您访问它时,它会导致未定义的行为。

在这种情况下,你很幸运,程序以你想象的方式运行。但很多时候,它不会带有未定义的行为。所以一般来说,避免做你做过的事情。