限制指针算术或比较的基本原理是什么?

What is the rationale for limitations on pointer arithmetic or comparison?

在C/C++中,只有当两个指向的对象是唯一完整对象的子对象时,才能执行两个指针的addition or subtraction on pointer is defined only if the resulting pointer lies within the original pointed complete object. Moreover, comparison

这种限制的原因是什么?

我认为是分段内存模型(参见here §1.2.1) could be one of the reasons but since compilers can actually define a total order on all pointers as demonstrated by this ,我对此表示怀疑。

原因是为了保持生成合理代码的可能性。这适用于具有平面内存模型的系统以及具有更复杂内存模型的系统。如果你禁止(不是很有用的)极端情况,比如从数组中添加或减去数组,并要求对象之间的指针的总顺序,你可以在生成的代码中跳过很多开销。

标准施加的限制允许编译器对指针运算做出假设并使用它来提高代码质量。它涵盖了在编译器中而不是在运行时静态计算事物,以及选择要使用的指令和寻址模式。例如,考虑一个有两个指针 p1p2 的程序。如果编译器可以得出它们指向不同的数据对象,它可以安全地假设任何基于 p1 的 no 操作将永远影响 p2 指向的对象。这允许编译器根据 p1 重新排序加载和存储,而不考虑基于 p2 的加载和存储,反之亦然。

您只是证明了可以取消限制 - 但忽略了它会带来成本(在内存和代码方面) - 这与 C 的目标背道而驰。

具体来说,差异需要有一个类型,即 ptrdiff_t,人们会认为它类似于 size_t。

在分段内存模型中,您(通常)间接地限制了对象的大小 - 假设以下答案:What's the real size of `size_t`, `uintptr_t`, `intptr_t` and `ptrdiff_t` type on 16-bit systems using segmented addressing mode? 是正确的。

因此,至少对于差异,删除该限制不仅会添加额外的指令以确保总订单 - 对于不重要的极端情况(如其他答案),而且还会花费双倍的内存来处理差异等。

C 被设计得更简约,而不是强制编译器在这种情况下花费内存和代码。 (在那些日子里,内存限制更为重要。)

显然还有其他好处——比如在混合来自不同数组的指针时检测错误的可能性。类似地,两个不同容器的混合迭代器在 C++ 中未定义(有一些小例外)——一些调试实现检测到此类错误。

存在程序和数据space分离的体系结构,根本不可能减去两个任意指针。指向函数或 const 静态数据的指针将位于与普通变量完全不同的地址 space。

即使您任意提供了不同地址 space 之间的排名,diff_t 类型也有可能需要更大的大小。而且比较或减去两个指针的过程会非常复杂。对于一种为速度而设计的语言,这是个坏主意。

原因是有些架构是分段内存的,不同对象的指针可能指向不同的内存段。两个指针之间的差异不一定有意义。

这一直追溯到准标准 C。C 基本原理没有明确提及这一点,但它暗示这就是原因,如果我们看看它在哪里解释了为什么使用负数组索引的基本原理是未定义的行为(C99 基本原理 5.10 6.5.6,强调我的):

In the case of p-1, on the other hand, an entire object would have to be allocated prior to the array of objects that p traverses, so decrement loops that run off the bottom of an array can fail. This restriction allows segmented architectures, for instance, to place objects at the start of a range of addressable memory.

既然 C 标准打算涵盖大多数处理器架构,它也应该涵盖这个: 想象一个架构(我知道一个,但不会说出它的名字),其中指针不仅仅是普通数字,而是类似于结构或 "descriptors"。这样的结构包含有关它指向的对象(其虚拟地址和大小)及其内部偏移量的信息。添加或减去指针会产生一个新结构,仅调整偏移字段;硬件禁止生成偏移量大于对象大小的结构。还有其他一些限制(比如初始描述符是如何产生的,还有哪些其他的修改方式),但与主题无关。

我想通过反问来回答这个问题。而不是问为什么指针加法和大部分的算术运算是不允许的,为什么指针只允许加减一个整数,post和指向同一个数组的指针的预增减和比较(或减法) ?它与算术运算的逻辑结果有关。 Adding/subtracting 指向指针 p 的整数 n 为我提供从当前指向的元素向前或向后方向的第 n 个元素的地址。类似地,减去指向同一个数组的 p1 和 p2 得到两个指针之间的元素数。 指针算术运算的定义与其指向的变量类型一致这一事实(或设计)是真正的天才之举。允许的操作以外的任何操作都违背了编程或哲学逻辑推理,因此是不允许的。

在大多数情况下,Stanadrd 将操作归类为调用未定义行为,因为:

  1. 可能有些平台定义行为的成本很高。如果代码试图进行超出对象边界的指针运算,分段架构可能会表现得很奇怪,一些编译器可能会通过测试 q-p.

  2. 的符号来评估 p > q
  3. 有些类型的编程在其中定义行为是无用的。许多类型的代码都可以很好地运行,而不依赖于标准给出的指针加法、减法或关系比较的形式。

  4. 为各种目的编写编译器的人应该能够识别用于此类目的的高质量编译器应该以可预测的方式运行的情况,并在适当的时候处理此类情况,无论标准是否强制他们这样做.

#1 和#2 都非常低,#3 被认为是 "gimme"。尽管编译器编写者通过寻找方法来破坏其行为由用于低级编程的质量实现定义的代码来炫耀他们的聪明才智已经成为一种时尚,但我认为标准的作者并不期望编译器编写者看到一个巨大的要求行为可预测的行为与那些几乎所有质量实现都被期望行为相同的行为之间的区别,但在那里让一些神秘的实现做其他事情可能是有用的。