2013-02-01 32 views
0

在下面的代码中,我尝试访问数组的'-1'元素,但没有得到任何运行时错误。当我访问数组的超出界限元素时,为什么不会出现运行时错误?

#include <stdio.h> 

int A[10] = {0}; 

int main(){ 

    A[-1] += 12; 

    printf("%d",A[-1]); 

    return 0; 
} 

当运行的代码,它输出12这意味着它是加入12向不存在的A [-1]。直到今天,每当我试图访问一个超出界限的元素,我都有一个运行时错误。我以前从来没有尝试过使用简单的代码。

任何人都可以解释为什么我的代码运行成功?

我把它运行在我的电脑上,也在ideone上运行,在两种情况下都成功运行。

+0

未定义的行为。如果'A'是一个指向第一个元素的指针,那么你可以这样做,但这更多的是一个负面的索引,而不是一个超出界限的东西。 – chris

+0

我也尝试访问A [11],并且它成功运行。 – 2147483647

+3

@ A.06:是的。 “未定义的行为”包括“它已成功运行”。未定义的行为还包括“可怕的崩溃”和“独角兽从您的电脑屏幕飞出”。它*字面意思*表示你不能以任何形式的可靠性来推理它的行为,这就是为什么UB正是你不想做的。 –

回答

1

你会看到,当你像这样分配一个变量时,它会着陆在堆栈上。 Stack在你调用的每个函数中保存有关局部变量的小包信息,用简单的话来说。运行时可以检查是否超出了分配堆栈的界限,但是如果在堆栈上的无效位置写入一些数据,则无法检查。堆可以如下所示:

[4个字节 - 一些PTR] [4个字节 - A的第一个元素] [4个字节 - A的第二个元素] ...

当你试图给-1th数组的元素,你实际上试图读取数组之前的四个字节(四个字节,因为它是一个int数组)。您可以覆盖堆栈中保存的一些数据 - 但仍然存在于有效进程的内存中,因此系统没有任何投诉。

尝试在Visual Studio中运行在释放模式验证码:

#include <stdio.h> 

int main(int argc, char * argv[]) 
{ 
    // NEVER DO IT ON PURPOSE! 
    int i = 0; 
    int A[5]; 

    A[-1] = 42; 
    printf("%d\n", i); 

    getchar(); 
    return 0; 
} 

编辑:响应意见。

我错过了这个事实,即A是全球性的。它不会被保存在堆栈中,而是在二进制模块的.data段中(大多数情况下),但其余的解释如下:A [-1]仍在进程内存中,因此赋值不会引发AV。但是,这样的赋值会覆盖某些东西,即A之前(可能是指针或二进制模块的其他部分),导致未定义的行为。

请注意,根据编译器(或编译器模式),我的示例可能工作,也可能不工作。例如,在调试模式下,程序返回0--我想,内存管理器会在堆栈帧之间插入一些哨兵数据来捕获缓冲区溢出/溢出等错误。

+3

除了definiton int A [10] = {0};'(在OP的代码中)在任何函数的主体之外,所以我怀疑'A'是在堆栈上创建的那种情况:) –

+0

啊,你是对的。不过,我猜想,这仍然是编写进程内存的问题,是什么让这段代码不会崩溃。 – Spook

+0

@Spook,这是一个合理的解释。只是不要指望这是原因。它可能只是编译器处于一个棘手的心情:) – chris

1

C和C++没有任何边界检查。它是语言的一部分。这是为了使语言更快地执行。

如果你想边界检查使用另一种语言。 Java也许?

随着您的代码执行,您只是幸运。

+0

如果你想边界检查,使用适当的容器和'at()'。 – chris

+0

但是,它们在A [-1]不存在时如何将12添加到A [-1]? – 2147483647

+0

@ A.06,它仍然存在于内存中,但试图推测未定义的行为是多么毫无意义。 – chris

1

在C++(和C)中,数组不检出范围索引。他们不是班级。

在C++ 11,但是你可以使用std::array<int,10>at()功能:

std::array<int,10> arr; 

arr.at(-1) = 100; //it throws std::out_of_range exception 

或者你可以使用std::vector<int>at()成员函数。

相关问题