我正在尝试理解静态分配对象的指针是如何工作的以及它们可能出错的地方。
我写了这段代码:
int* pinf = NULL;
for (int i = 0; i<1;i++) {
int inf = 4;
pinf = &inf;
}
cout<<"inf"<< (*pinf)<<endl;
我感到很惊讶,因为我认为inf
会在程序离开块并且指针指向不再存在的东西时消失。我在尝试访问pinf
时预计会出现分段错误。该计划的哪个阶段会inf
死亡?
答案 0 :(得分:7)
您的理解是正确的。当您离开循环范围时,inf
消失,因此访问*pinf
会产生未定义的行为。未定义的行为意味着编译器和/或程序可以执行任何操作,这可能会导致崩溃,或者在这种情况下可能只是简单地进行操作。
这是因为inf
在堆栈上。即使它超出范围pinf
仍然指向堆栈上的可用内存位置。就运行时而言,堆栈地址很好,并且编译器不会费心插入代码来验证您是否没有访问超出堆栈末尾的位置。在速度设计的语言中,这将是非常昂贵的。
因此,您必须非常小心,以避免未定义的行为。 C和C ++并不像Java或C#那样非常好,非法操作几乎总是会产生立即异常并导致程序崩溃。程序员必须保持警惕,因为编译器会错过你所犯的各种基本错误。
答案 1 :(得分:3)
您使用所谓的Dangling pointer。它将导致C ++标准的未定义行为。
答案 2 :(得分:2)
它可能永远不会死,因为pinf将指向堆栈上的 某些 。
堆栈通常不会缩小。
修改它,你几乎可以保证覆盖。
答案 3 :(得分:2)
如果你问这个:
int main() {
int* pinf = NULL;
for (int i = 0; i<1;i++){
int inf = 4;
pinf = &inf;
}
cout<<"inf"<< (*pinf)<<endl;
}
然后你所拥有的是未定义的行为。自动分配(非非静态)对象inf已超出范围,并且当您通过指针访问它时,概念上已被销毁。在这种情况下,任何事情都可能发生,包括它似乎“工作”。
答案 4 :(得分:1)
您不一定会得到SIGSEGV(分段错误)。 inf
内存可能在堆栈中分配。并且堆栈内存区域可能仍然分配给您的进程,因此,这可能就是您没有遇到seg错误的原因。
答案 5 :(得分:1)
行为是未定义的,但在实践中,“破坏”int
是一个noop,因此大多数编译器会将数字单独留在堆栈上,直到其他东西出现以重用该特定的插槽。
某些编译器在调试模式下超出范围时可能会将int设置为0xDEADBEEF
(或某些此类垃圾),但这不会使cout << ...
失败;它只会打印出荒谬的价值。
答案 6 :(得分:1)
当它到达你的cout线时,内存可能会或可能不会包含4。它可能严重意外地包含4。 :)
首先要做的事情是:您的操作系统只能检测在页面边界上误入歧途的内存访问。所以,如果你的距离是4k或8k或16k或更多。 (有一天在Linux系统上检查/proc/self/maps
以查看进程的内存布局;允许列出范围内的任何地址,不允许在列出的范围之外的任何地址。受保护内存CPU上的每个现代操作系统都将支持类似的机制,所以即使你对Linux不感兴趣它也会很有启发性。我只知道它在Linux上很容易。)因此,当你的数据太小时,操作系统无法帮助你。 / p>
此外,您的int inf = 4;
可能会隐藏在您计划的.rodata
,.data
或.text
段中。静态变量可以填充到这些部分中的任何一部分(我不知道编译器/链接器如何决定;我认为它很神奇)因此它们在程序的整个持续时间内都是有效的。下次访问Unix系统时,请检查size /bin/sh
,了解将哪些数据放入哪些部分。 (并查看readelf(1)
以获取太多信息。objdump(1)
如果您使用的是较旧的系统。)
如果您将inf = 4
更改为inf = i
,那么存储将在堆栈中分配,您可以更快地被覆盖。
答案 7 :(得分:0)
当您指向的内存页面不再对该进程有效时,会发生保护错误。
幸运的是,大多数操作系统都没有为每个整数的堆栈空间创建一个单独的页面。