指针减法和替代方案

Pointer Subtraction and an Alternative

本文关键字:方案 指针      更新时间:2023-10-16

当使用数组时,标准算法(在C和C++中)通常会返回指向元素的指针。 有时拥有元素的索引很方便,也许索引到另一个数组中,我通常通过从指针中减去数组的开头来获得它:

int arr[100];
int *addressICareAbout = f(arr, 100);
size_t index = addressICareAbout - arr;

这似乎总是足够简单和有效。 然而,最近有人向我指出,指针减法实际上返回了一个ptrdiff_t,原则上,如果"index"不适合ptrdiff_t,可能会出现问题。 我真的不相信任何实现都会反常到允许创建如此大的arr(从而导致此类问题),但是这里接受的答案承认这是可能的,我没有发现任何证据表明并非如此。 因此,我对此表示认命(除非有人能说服我),并且会小心翼翼地前进。 这个答案提出了一种相当复杂的"安全"获取索引的方法;真的没有比这更好的了吗?

也就是说,我对C++中可能的解决方法感到困惑。 我们有std::distance,但std::distance(arr, addressICareAbout)保证是明确的吗? 一方面,(指向第一个元素的指针)arr可以递增以达到addressICareAbout(对吗?),但另一方面,std::distance应该返回一个ptrdiff_t。 标准容器的迭代器可能(大概)有同样的问题。

您极不可能有两个指向同一数组的指针,其中差异不适合ptrdiff_t。

在 64 位实现中,ptrdiff_t 是 64 位签名的,因此您需要一个 80 亿 GB 的数组。在 32 位实现中,通常您的总地址空间限制为 3 GB,如果幸运的话,为 3 1/4 GB(重要的是地址空间,而不是 RAM),因此您需要一个超过 2 GB 的数组,这不会留下太多其他东西。而且 malloc 很可能首先会拒绝分配这种大小的数组。当然是你的判断。

虽然 std::d istance 有优势,但我怀疑它与 ptrdiff_t 具有相同的理论问题,因为距离可以是正数和负数,而且它可能不是 32 位实现上的 64 位类型。

请注意,如果您可以在 32 位实现上分配一个 3 GB 数组,并且该数组的第一个和最后一个元素有两个 int*,那么即使结果适合ptrdiff_t,如果指针差计算不正确,我也不会感到惊讶。

可能的解决方法:

将两个指针投射到uintptr_t、减去和除以sizeof (T)自己。 这不是精确可移植的,但它保证永远不会是未定义的行为,并且大多数系统都以使其工作的方式指定整数<>指针转换。

真正可移植(但效率较低)的解决方法:

使用备用基指针。 如果数组超过2<<30个元素,则可以合法地计算step = p1 + (2<<30),使用关系运算符查看是否p2 > step,如果是,则计算偏移量(2u << 30) + uintptr_t(distance(step, p2))请注意,递归调用可能需要执行另一个步骤。

我没有发现任何证据表明并非如此。

幸运的是,这里有证据:http://en.cppreference.com/w/cpp/types/size_t

std::size_t可以存储理论上可能的任何类型的对象(包括数组)的最大大小。大小不能用std::size_t表示的类型格式不正确(自 C++14 起)