我一直在阅读 K&R 关于 C 的书,发现 C 中的指针运算允许访问数组末尾之外的一个元素。我知道 C 几乎可以用内存做任何事情,但我就是不明白,这种特性的目的是什么?
4 回答
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 可能不是通用应用程序编程的好工具。
通常,表示“结束”位置很有用,它是实际分配之后的位置,因此您可以编写如下代码:
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 标准说分配是不允许的。
如果您在分配的内存之外读取或写入,则 C 标准会说它的“未定义行为”。这意味着几乎任何事情都可能发生,可能是现在,可能在一周内,或者可能在 5 年后,或者可能永远不会发生,而你却侥幸逃脱。
我的老板有几句格言:“没有正确的 C 程序,只有一个还没有出错的东西”
他总是对的。
例如,您可以远远超出数组 1 的范围`
int main()
{
char *string = "string";
int i = 0;
for(i=0; i< 10;i++)
{
printf("%c\n", string[i]);
}
return 0;
}
将在单词字符串结束后打印垃圾,无论之前坐在内存中的任何内容。