2

因此,正如我从Michael Burr对此答案的评论中了解到的那样,C 标准不支持从数组中第一个元素(我想包括任何分配的内存)的指针中减去整数。

来自组合 C99 + TC1 + TC2 (pdf) 的第 6.5.6 节:

如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为是未定义的。

我喜欢指针算术,但这从来不是我以前担心过的事情。我一直假设:

 int a[1];
 int * b = a - 3;
 int * c = b + 3;

那个c == a

因此,虽然我相信我以前做过这种事情并且没有被咬,但这一定是由于我使用过的各种编译器的好意——它们已经超出了标准所要求的使指针算术按我想象的方式工作。

所以我的问题是,这有多普遍?是否有常用的编译器对我不那么友好?超出数组边界的正确指针算术是事实上的标准吗?

4

4 回答 4

7

MSDOS FAR 指针有这样的问题,通常通过在实模式中“巧妙地”使用段寄存器与偏移寄存器的重叠来解决。那里的效果是 16 位段是左移 4 位,并添加到 16 位偏移量,这给出了一个可以寻址 1MB 的 20 位物理地址,这已经足够了,因为每个人都知道没有人会需要高达 640KB 的 RAM。;-)

在保护模式下,段寄存器实际上是内存描述符表的索引。典型的 DOS 扩展运行时通常会安排一些事情,以便可以像在实模式下一样对待许多段,这使得从实模式移植代码变得容易。但它有一些缺陷。首先,分配之前的段不是分配的一部分,因此它的描述符甚至可能无效。

在处于保护模式的 80286 上,仅使用会导致加载无效描述符的值加载段寄存器将导致异常,无论描述符是否实际用于引用内存。

在分配后的一个字节处可能会发生类似的问题。指针上的最后一个 ++ 可能已经转移到段寄存器,导致它加载新的描述符。在这种情况下,期望内存分配器可以在分配范围的末尾安排一个安全描述符是合理的,但期望它安排更多的内容是不合理的。

于 2009-04-24T06:57:10.030 回答
4

这不是标准的“实现定义”,这是标准的“未定义”。这意味着你不能指望一个支持它的编译器,你不能说,“好吧,这个代码在编译器 X 上是安全的”。通过调用未定义的行为,您的程序是未定义的。

实际的答案不是“我如何(在哪里、何时、在什么编译器上)我可以摆脱这个”;实际的答案是“不要这样做”。

于 2009-04-24T06:56:17.533 回答
1

另一个原因是,有可选的保守垃圾收集器(如 boehm-weiser GC)假定指针始终在分配的范围内,如果不在分配范围内,则允许它们随时释放内存。

有一个流行的商业质量和使用的库确实打破了这一假设,它是 HP 的 Judy Trees 库,它使用指针算法来实现非常复杂的哈希结构。

于 2009-11-06T22:57:03.710 回答
0

用于 TI Explorer 的ZETA-C;指针被实现为数组和索引或置换数组,IIRC,所以你的例子可能不起作用。从zcprim>pointer-subtractin开始zcprim.lisp弄清楚行为会是什么。不知道这是否符合标准,但我的印象是正确的。

于 2009-04-24T21:13:52.823 回答