在C语言中访问数组结尾以外的元素

20

我一直在阅读K&R的C语言书籍,发现C语言中的指针算术运算可以访问数组末尾的下一个元素。我知道C语言几乎可以对内存做任何事情,但我不明白这种特性的目的是什么?


1
请参考以下问题:https://dev59.com/RHNA5IYBdhLWcg3wX8rk - Adam Rosenfield
4个回答

26

C语言不允许访问数组末尾之外的内存,但是它允许指针指向数组末尾之外的一个元素。这种区别非常重要。

因此,下面的代码是可以的:

char array[N];
char *p;
char *end;

for (p = array, end = array + N; p < end; ++p)
    do_something(p);

(执行*end会导致错误。)

这正显示了为什么这个特性是有用的:指向数组结束后(不存在的)元素的指针可用于比较,例如在循环中。

从技术上讲,这是C标准允许的全部内容。然而,在实践中,C实现(编译器和运行时)并不检查您是否访问了超出数组末尾的内存,无论它是一个元素还是多个元素。必须进行边界检查,这会减慢程序的执行速度。C最适合的程序类型(系统编程,通用库)往往更受益于速度,而不是安全边界检查所能提供的安全性。

这意味着C也许不是通用应用程序编程的好工具。


19

通常,标记“结束”位置很有用,它比实际分配多一个位置,这样您可以编写以下代码:

 char * end = begin + size;
 for (char * curr = begin; curr < /* or != */ end ; ++curr) {
    /* do something in the loop */
 }

C标准明确表示该元素是一个有效的内存地址,但解引用它仍不是一个好主意。

为什么它有这个保证?假设你有一台拥有2^16个字节(0000-FFFF,16位指针)的内存的机器。假设你创建了一个16字节的数组。那么内存可以在FFF0处分配吗?

有16个连续的空闲字节,但问题在于:

begin + size == FFF0 + 10 (16 in hex) == 10000

由于指针大小的限制,在循环中被包装为0000。现在考虑循环条件:

curr < end == FFF0 < 0000 == false

循环不执行任何操作而是遍历数组,这将破坏大量代码,因此C标准规定分配不允许。


1
如果你读取或写入超出分配内存的范围,那么C标准称其为“未定义行为”。 这意味着几乎任何事情都可能发生,也许现在,也许一周后,也许5年后,甚至可能永远不会发生,而你却逃脱了惩罚。
我的老板有几个格言: “不存在正确的C程序,只有还没有出错的程序” “关于内存损坏,唯一明智的做法就是保持沉默。”
他总是正确的。

-3

你可以超出数组的范围,例如:

int main()
{
        char *string = "string";
        int i = 0;
        for(i=0; i< 10;i++)
        {
                printf("%c\n", string[i]);
        }
        return 0;
}

在单词字符串结束后,将打印垃圾内容,无论之前存储在内存中的是什么。


7
Undefined Behaviour的本质就是它可能会输出垃圾信息,格式化你的硬盘,或导致恶魔从你的鼻子里飞出来;需要注意的是,翻译过程中不能改变原意。 - aib
嗯,仅从内存位置读取不太可能格式化您的硬盘或导致恶魔从您的鼻子飞出。然而,写入它... - Andrei Krotkov
3
即使读取一个错误的指针,也可能会导致程序在未来崩溃。请参阅http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx。 - Eclipse
2
如果它是一个内存映射的设备I/O端口,仅仅通过读取操作就可能会导致一些问题发生。但当然,在内核之外这是不可能的。 - user25148
或者在嵌入式系统中。我的硬件设计合作伙伴喜欢构建只写和只读端口,不加区分的访问可能会导致硬件执行意外操作,通常只在演示时表现出症状。 - RBerteig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接