|
|
1
4682
你租了个旅馆房间。你把一本书放在床头柜最上面的抽屉里睡觉。你第二天早上退房,但忘了把钥匙还给我。你偷了钥匙! 一周后,你回到酒店,不办理入住手续,拿着偷来的钥匙偷偷溜进你的旧房间,在抽屉里找。你的书还在那儿。令人吃惊的! 怎么会这样?如果你还没有租过房间,难道酒店房间抽屉里的东西就不可接近吗? 很明显,这种情况在现实世界中是可以发生的,没问题。当你不再被授权进入房间时,没有神秘的力量使你的书消失。也没有神秘的力量阻止你带着偷来的钥匙进入房间。 酒店管理层 必修的 把你的书拿走。你没有和他们签合同说如果你把东西留下,他们会给你撕碎的。如果你非法用偷来的钥匙重新进入房间取回,酒店保安人员不会 必修的 抓住你偷偷溜进来。你没有和他们签订合同,上面写着“如果我以后想溜回我的房间,你必须阻止我。”相反,你和他们签订了一份合同,上面写着“我保证以后不溜回我的房间”,一份合同 你破产了 . 在这种情况下 任何事情都有可能发生 . 书可以在那儿——你很幸运。别人的书可以在那里,你的书可以在酒店的火炉里。你进来的时候可能有人在那里,把你的书撕成碎片。旅馆本可以把桌子和书全部搬走,换成一个衣柜。整个酒店可能就要被拆掉,换成一个足球场,你会在偷偷摸摸的时候死于爆炸。 你不知道会发生什么;当你离开酒店,偷了一把钥匙以后非法使用时,你放弃了生活在一个可预测的、安全的世界的权利,因为 你 选择打破系统规则。 C++不是一种安全的语言 . 它会让你愉快地打破制度的规则。如果你试图做一些非法的、愚蠢的事情,比如回到房间里,你没有被授权进入并翻找一张甚至不在那里的桌子,C++就不会阻止你。比C++更安全的语言通过限制你的力量来解决这个问题,例如,对键有更严格的控制。 更新天哪,这个答案引起了很多关注。(我不知道为什么——我认为这只是一个“有趣”的小比喻,但无论如何。) 我想用一些技术性的想法来更新这一点可能是有关系的。 编译器的任务是生成代码来管理由该程序操作的数据的存储。有很多不同的方法生成代码来管理内存,但随着时间的推移,两种基本技术已经根深蒂固。 首先是要有某种“长寿命”的存储区,其中存储区中每个字节的“寿命”(即与某个程序变量有效关联的时间段)不容易提前预测。编译器生成对“堆管理器”的调用,该管理器知道如何在需要时动态分配存储,并在不再需要时回收存储。 第二种方法是有一个“短命”存储区域,其中每个字节的生存期是众所周知的。在这里,生命周期遵循一种“嵌套”模式。这些短期变量中寿命最长的将在任何其他短期变量之前分配,并在最后释放。寿命较短的变量将在寿命最长的变量之后分配,并在它们之前释放。这些寿命较短的变量的生存期是在寿命较长的变量的生存期内嵌套的。 局部变量遵循后一种模式;当一个方法被输入时,它的局部变量会被激活。当该方法调用另一个方法时,新方法的局部变量将激活。在第一个方法的局部变量死之前,它们就死了。与局部变量相关的存储的生命周期的开始和结束的相对顺序可以提前计算出来。 由于这个原因,局部变量通常作为“堆栈”数据结构上的存储生成,因为堆栈具有这样的属性:第一个被推到它上面的东西将是最后一个被弹出的东西。 就像酒店决定只按顺序出租房间一样,除非每个房间号都比您的退房号高,否则您无法退房。 所以让我们考虑一下堆栈。在许多操作系统中,每个线程都有一个堆栈,并且堆栈被分配为一定的固定大小。当你调用一个方法时,东西被推到堆栈上。如果你然后把一个指向堆栈的指针从你的方法中传出来,就像最初的海报所做的那样,那只是一个指向一些完全有效的百万字节内存块中间的指针。在我们的类比中,你是从酒店退房的;当你退房的时候,你只是从人数最多的房间退房。如果没有其他人在你之后登记入住,而你又非法回到房间,那么你所有的东西肯定还在那里 在这个特别的酒店 。 我们使用堆栈作为临时存储,因为它们非常便宜和容易。不需要使用C++来实现本地存储的堆栈;它可以使用堆。不会,因为那样会使程序变慢。 C++的实现不需要将未被保留的堆栈中的垃圾留下,以便以后可以非法返回;对于编译器来说,生成代码是完全合法的,在您刚刚腾出的“房间”中,所有的代码都会变回零。又不是因为那会很贵。 不需要实现C++,以确保栈在逻辑上收缩时,仍然有效的地址仍然映射到内存中。实现可以告诉操作系统“我们已经使用完这个堆栈页了。除非我另有说明,否则如果有人触碰先前有效的堆栈页,将发出一个异常来破坏进程”。同样,实现并没有真正做到这一点,因为它是缓慢和不必要的。 相反,实现会让您犯错误并逃脱惩罚。大多数时候。直到有一天,真正可怕的事情发生了,整个过程爆发了。 这是有问题的。有很多规则,很容易不小心打破它们。我当然有很多次了。更糟糕的是,当内存在崩溃发生后数十亿纳秒内被检测到被破坏时,这个问题往往才浮出水面,而此时很难找出是谁把它弄糟了。 更多的内存安全语言通过限制你的能力来解决这个问题。在“普通的”C中,根本无法获取本地地址并将其返回或存储以备以后使用。你可以取一个本地人的地址,但是语言设计得很巧妙,所以在本地人的生命周期结束后不可能使用它。为了获取本地地址并将其传回,必须将编译器置于特殊的“不安全”模式中, 和 在你的程序中加上“不安全”这个词,提醒大家注意你可能在做一些可能违反规则的危险的事情。 进一步阅读:
|
|
|
2
269
你在这里所做的只是读写记忆
用于
地址是
当你回来的时候
如果你想知道为什么编译器不抱怨,可能是因为
简而言之:这通常不起作用,但有时是偶然的。 |
|
3
146
因为存储空间还没有被踩踏。别指望那种行为。 |
|
|
4
78
在所有答案的基础上增加一点: 如果你这样做:
输出可能是:7 这是因为在从foo()返回之后,堆栈被释放,然后被boo()重用。 如果对可执行文件进行去汇编,您将清楚地看到它。 |
|
5
67
在C++中,你 可以 访问任何地址,但这并不意味着你 应该 . 您正在访问的地址不再有效。它 作品 因为foo返回后没有其他东西扰乱内存,但在许多情况下可能会崩溃。尝试使用分析程序 Valgrind ,甚至只是优化编译,然后看… |
|
|
6
64
您从不通过访问无效内存抛出C++异常。您只是给出了引用任意内存位置的一般概念的一个示例。我也可以这样做:
在这里,我只是把123456当作一个double的地址并给它写信。任何事情都有可能发生:
您设置它的方式更合理一点,返回的地址指向内存的有效区域,因为它可能就在堆栈的下面一点,但它仍然是一个无效的位置,您无法以确定的方式访问它。
在正常程序执行期间,没有人会自动为您检查这样的内存地址的语义有效性。但是,内存调试器如
|
|
7
28
你在启用乐观主义者的情况下编译程序了吗?这个
但我同意马克B的观点,即最终的行为是不确定的。 |
|
|
8
22
你的问题与此无关
范围
. 在您显示的代码中,函数
你遇到的问题是为什么程序在引用非法内存时没有发出错误信号。这是因为C++标准没有规定非法内存和合法内存之间非常明确的界限。引用弹出堆栈中的内容有时会导致错误,有时不会。这要看情况。别指望这种行为。假设在编程时它总是会导致错误,但假设在调试时它永远不会发出错误信号。 |
|
|
9
17
您只是返回了一个内存地址,这是允许的,但可能是一个错误。 是的,如果您试图取消引用该内存地址,您将有未定义的行为。
|
|
|
10
16
它之所以起作用,是因为自a被放在那里以来,堆栈还没有被改变。
在访问之前调用一些其他函数(它们也在调用其他函数)
|
|
|
11
16
那很经典 未定义行为 这是两天前讨论过的——在网站上搜索一下。简而言之,你很幸运,但是任何事情都有可能发生,你的代码对内存的访问无效。 |
|
|
12
16
正如alex指出的,这种行为是未定义的——事实上,大多数编译器都会警告不要这样做,因为这是一种容易导致崩溃的方法。 比如你的怪异行为 可能的 要获取,请尝试以下示例:
这会打印出“y=123”,但结果可能会有所不同(真的!)。指针正在删除其他不相关的局部变量。 |
|
|
13
16
注意所有警告。不要只解决错误。
这是C++的力量。你应该关心记忆力。与
|
|
|
14
15
您实际上调用了未定义的行为。 返回临时作品的地址,但由于临时作品在函数结束时被销毁,因此访问它们的结果将是未定义的。
所以你没有修改
|
|
15
13
在典型的编译器实现中,可以将代码看作是“用
以前是
被一个“.另外,如果您将一个新的函数调用添加到一个约束本地
然而,这是 未定义 行为和你不应该依赖它来工作! |
|
|
16
13
可以,因为
你所做的被描述为 未定义的行为 . 结果无法预测。 |
|
|
17
11
正确的东西(?)如果使用::printf而不是cout,控制台输出可能会发生显著变化。 您可以在下面的代码中使用调试器(在x86、32位、msvisual studio上测试):
|
|
|
18
4
从函数返回后,所有标识符都会被销毁,而不是保存在内存位置中的值,如果没有标识符,则无法定位这些值,但该位置仍然包含前一个函数存储的值。
所以,这里是函数
让我举一个现实世界的例子: 假设一个人把钱藏在一个地方,告诉你地点。过了一段时间,那个告诉你钱的地点的人死了。但你还是可以拿到那笔隐藏的钱。 |
|
|
19
2
这是“肮脏”的使用内存地址的方式。当您返回一个地址(指针)时,您不知道它是否属于函数的本地作用域。只是一个地址。既然您调用了“foo”函数,那么“a”的地址(内存位置)已经在应用程序(进程)的(至少目前是安全的)可寻址内存中分配到了那里。在“foo”函数返回后,“a”的地址可以被认为是“脏的”,但它在那里,没有被清除,也没有被程序其他部分的表达式干扰/修改(至少在这种特定情况下)。一个C/C++编译器不会阻止你这样的“脏”访问(如果你在意的话,也许会警告你)。您可以安全地使用(更新)程序实例(进程)的数据段中的任何内存位置,除非您通过某种方式保护该地址。 |
|
|
MaPo · Linux,设置锁定ICMP_过滤器选项 1 年前 |
|
Doohyeon Won · 内联函数上的奇怪现象?[关闭] 1 年前 |
|
|
Bobby · 复合字面值总是左值吗? 1 年前 |
|
9-Pin · C: 嵌套结构的堆栈内存分配 1 年前 |