为什么这个工作?
#include <iostream> using namespace std; int main() { float* tab[3]; int i = 0; while(i < 3) { tab[i] = new float[3-i]; i++; } cout << tab[2][7] << endl; tab[2][7] = 6.87; cout << tab[2][7] << endl; i = 0; while(i < 3) delete[] tab[i]; }
而这一个不?
#include <iostream> using namespace std; int main() { float* tab = new float[3]; cout << tab[7] << endl; tab[7] = 6.87; cout << tab[7] << endl; delete[] tab; }
我在Win XP和MS VS 2008上都试过这两个程序,它们都没有编译错误,第一个没有任何错误。 第二个popup一些错误窗口,但我不记得它,不能重现(目前无法访问Windows)。
我也用g ++在Linux上(Kubuntu 10.10和预编译的内核包版本2.6.35.23.25)尝试了它们,并且编译和运行都没有任何错误。
为什么? 不应该有任何类似“错误访问未分配的内存”的popup窗口?
我知道它应该(而且,幸运的是)编译没有错误,但我认为它不应该运行没有他们…为什么第二个例子在Windows上,而不是在Linux上犯错误?
使用未分配的内存会导致未定义的行为。 即使是在同一个系统和编译器中,你也不能期望会发生什么,更不用说跨硬件和编译器的不同组合了。
该程序可能会立即崩溃,它可能会工作一段时间,然后失败,它甚至可能看起来工作完美。
访问你不拥有的内存始终是一个编程错误,虽然。 不要认为“有时候有效”的正确操作的出现,把它想成是“我真的不吉利,我的错误不会很快出现”。
而另一个答案,除了马克,没有错,他们也不完全正确。 通过在程序中显式分配数据的末尾访问数据,导致“未定义的行为”。 它可以做任何事情,包括“工作”。
当我开始写这个的时候,史蒂夫的回答是不存在的。
他们都进行了越界数组访问 – 你有一个3浮点数组的数组,你正在访问第8个数组。 这肯定会崩溃。
但是,与Java或其他一些托管语言不同的是,对于每个数组访问没有明确的边界检查(因为性能成本太高)。 所以检查你的唯一边界是你的MMU。 如果您最终访问不属于您的应用程序的内存,则会崩溃。 如果你碰到没有分配的内存,但仍然是你的进程的一部分(例如可能是一个守卫的话),它会默默地成功。 这是一个非常难以追踪的错误的好方法。
边界检查是关键。 尽可能做到这一点。
在第一个例子中,tab [2]有一个指向有效内存的值。 标签[2] +7没有分配,但它可能是。 没有seg-fault。
在第二个选项卡[7]没有值…它是随机位(可能是零,或0xDEADBEEF或只是最后有什么值)。 这几乎肯定不会指向这个应用程序访问的内存。 因此:繁荣。
内存访问保护不是很细粒度。 当你分配一些内存时,你会得到一整页的内存分配给你的程序。 当您尝试访问额外的内存时,可能会成功,但是您也可能会运行分配给您的程序的其他内存。
这就是缓冲区溢出的原因。 在很多情况下,它可以预测你的数组被用于多少额外的内存。 如果我能控制你放在那里的东西,我可以覆盖你不想覆盖的数据。 如果我可以覆盖你的调用栈,那么我可以在你的过程上下文中执行任何我想要的代码。 如果这是一个作为管理员用户运行的服务,那么我有一个本地特权升级。 如果这是某种面向互联网的服务,那么我有一个远程执行攻击。
你最好的选择是使用更强大的结构,比如std :: vector,除非你有使用数组的特定目的。 (即使如此,你也许能够摆脱载体 )。