为什么这样做?
#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尝试了两个程序,两个程序都编译没有错误,第一个程序运行没有任何错误。第二个弹出一些错误窗口,但是我无法记住它并且无法重现(目前无法访问Windows)。
我在Linux(带有预编译内核包版本2.6.35.23.25的Kubuntu 10.10)上使用g ++尝试了它们,并且编译和运行都没有任何错误。
为什么呢?不应该有任何弹出窗口,例如&#34;错误访问未分配的内存&#34;?
我知道它应该(并且,幸运的是)编译没有错误,但我认为它不应该在没有它们的情况下运行......为什么第二个例子在Windows上而不是在Linux上发生错误?
答案 0 :(得分:9)
使用未分配的内存会导致未定义的行为。即使在相同的系统和编译器上执行此操作,您也不会期望会发生什么,更不用说跨硬件和编译器的不同组合了。
程序可能会立即崩溃,它可能会工作一段时间然后再失败,它甚至可能看起来完美无缺。
访问您不拥有的内存总是一个编程错误。不要把正确操作的外观想象为“它有时会起作用”,把它想象成“我真的很不走运,而且我的虫子很快就不会出现”。
答案 1 :(得分:7)
虽然除了马克之外的其他答案都没有错,但他们也不完全正确。通过在程序中明确分配的内容之后访问数据,您正在做的是导致“未定义的行为”。它可以做任何事情,包括“工作”。
当我开始写这篇文章时,史蒂夫的回答并不存在。答案 2 :(得分:5)
它们都进行了越界数组访问 - 你有一个包含3个浮点指针的数组,你正在访问第8个数组。这肯定会崩溃。
但是,与Java或其他一些托管语言不同,没有明确的边界检查每个数组访问(因为它的性能成本太高)。所以检查你的唯一界限是你的MMU。如果您最终访问不属于您的应用程序的内存,您将崩溃。如果你点击了未分配的内存,但仍然恰好是你的过程的一部分(例如,可能是一个保护词),它将默默地成功。对于非常难以追踪的错误,这是一个很好的秘诀。
界限检查是关键。只要你能做就做。
答案 3 :(得分:1)
在第一个示例中,tab [2]具有指向有效内存的值。 tab [2] +7未分配,但它可能是。没有seg-fault。
在第二个中,tab [7]没有值......它是随机位(可能是零,或者是0xDEADBEEF,或者只是最后的值)。这几乎肯定不会指向对此应用程序有效的内存。因此:热潮。
答案 4 :(得分:0)
内存访问保护不是很细。分配一些内存时,可以获得分配给程序的整页内存。当您尝试访问该额外内存时,它可能会成功,但您也可能会运行分配给您的程序的其他内存。
这就是缓冲区溢出作为攻击的原因。在许多情况下,可以预测数组使用后的额外内存。如果我可以控制你放在那里的东西,我可以覆盖你不希望我覆盖的数据。如果我可以覆盖你的调用堆栈,那么我可以在你的进程上下文中执行我想要的任何代码。如果这是以管理员用户身份运行的服务,那么我将进行本地权限升级。如果这是某种面向互联网的服务,那么我就有远程执行攻击。
您最好的选择是使用更强大的结构,如std :: vector,除非您有使用数组的特定目的。 (即便如此,你也许可以get away with vectors)。