24

我在 C 中有这段代码,它包含一堆chars

#include<stdio.h> 
# define NEWLINE '\n'
int main()
{

char c;
char str[6];
int i = 0;
while( ((c = getchar()) != NEWLINE))
{
        str[i] = c;
        ++i;
        printf("%d\n", i);
}

return 0;
}

输入是:testtesttest

输出:1 2 3 4 5 6 7 8 117 118 119 120

我的问题是:

  1. 尽管我明显超出了数组的容量,为什么我没有得到越界(分段错误)异常?

  2. 为什么输出中的数字突然跳到非常大的数字?

我在 C++ 中尝试过这个并得到了相同的行为。谁能解释一下这是什么原因?

4

7 回答 7

32
  1. C 不检查数组边界。仅当您尝试取消引用指向您的程序无权访问的内存的指针时,才会发生分段错误。简单地越过数组的末尾不太可能导致这种行为。未定义的行为就是这样 - 未定义。它可能看起来工作得很好,但你不应该依赖它的安全性。
  2. 您的程序通过访问数组末尾的内存来导致未定义的行为。在这种情况下,看起来您的其中一个str[i] = c写入覆盖了i.
  3. 在这种情况下,C++ 具有与 C 相同的规则。
于 2012-02-04T00:11:48.483 回答
6

当您访问数组索引时,C 和 C++ 不进行边界检查。仅当您尝试读取或写入未分配的页面(或尝试在不允许的页面上执行某些操作,例如尝试写入只读页面)时才会发生分段错误,但由于页面通常相当大(几千字节的倍数;在 Mac OS 上,是 4 KB 的倍数),它通常会给您留下很多溢出的空间。

如果您的数组在堆栈上(就像您的一样),则可能会更糟,因为堆栈通常非常大(最多几兆字节)。这也是安全问题的原因:写入堆栈上的数组边界可能会覆盖函数的返回地址并导致任意代码执行(著名的“缓冲区溢出”安全漏洞)。

当您阅读时获得的值正是这个特定地方恰好存在的值。它们是完全未定义的。

如果您使用 C++(并且有幸使用 C++11),标准定义了std::array<T, N>类型,这是一个知道其边界的数组。at如果您尝试阅读它的末尾,该方法将抛出。

于 2012-02-04T00:11:14.040 回答
3

C 不检查数组边界。

事实上,分段错误并不是特别是由于超出数组边界而产生的运行时错误。相反,它是操作系统提供的内存保护的结果。当您的进程尝试访问不属于它的内存时,或者如果它尝试访问不存在的内存地址时,就会发生这种情况。

于 2012-02-04T00:11:38.440 回答
1

因为 C/C++ 不检查边界。

数组是指向内存中某个位置的内部指针。当您调用arr[index]它时,它的作用是:

type value = *(arr + index);

结果是大数字(不一定),因为它们是垃圾值。就像一个未初始化的变量。

于 2012-02-04T00:09:09.740 回答
1

写入数组边界之外(实际上即使只是执行指针算术/数组下标,即使您不使用结果来读取或写入任何内容)会导致未定义的行为。未定义的行为不是报告或可报告的错误;这意味着您的程序可以做任何事情。这是非常危险的,你有责任避免它。C 不是 Java/Python/等。

于 2012-02-04T00:12:45.570 回答
1

内存分配比看起来更复杂。在这种情况下,变量“str”在堆栈上,紧挨着其他变量,因此它后面没有未分配的内存。内存通常也是字对齐的(一个“字”是四到八个字节。)您可能弄乱了另一个变量的值,或者一些“填充”(添加空白以保持字对齐)或其他完全.

就像 R.. 所说,这是未定义的行为。越界条件可能导致段错误......或者它们可能导致静默内存损坏。如果您正在修改已分配的内存,则操作系统不会捕获到。这就是为什么越界错误在 C 语言中如此隐蔽的原因。

于 2012-02-04T00:17:45.783 回答
1

你必须像这样编译:

gcc -fsanitize=address -ggdb -o test test.c

这里有更多信息。

于 2015-09-05T19:08:54.920 回答