如何比较C指针?

最近,我写了一些代码来比较像这样的指针:

if(p1+len < p2) 

但是,有些工作人员说我应该这样写:

 if(p2-p1 > len) 

为了安全起见。 这里, p1p2char *指针, len是整数。 我不知道。那是对的吗?

EDIT1:当然, p1p2指向同一个内存对象时乞讨。

EDIT2:就在一分钟之前,我在我的代码中找到了这个问题的bogo (约3K行),因为len太大了, p1+len不能存储在4个字节的指针中,所以p1 + len <p2但实际上它不应该,所以我认为我们应该在某些情况下比较像这样的指针:

 if(p2  (uint32_t)len) 

通常,只有指针指向同一个内存对象的部分(或者超过对象末尾的一个位置)时,才能安全地比较指针。 当p1p1 + lenp2都符合此规则时,两个if -tests都是等效的,所以你不必担心。 另一方面,如果只知道p1p2符合这个规则,并且p1 + len可能在结束之后太远,那么只有if(p1-p2 > len)是安全的。 (但我无法想象你的情况。我认为p1指向某个内存块的开头,而p1 + len指向它结束后的位置,对吧?)

他们可能一直在考虑的是整数运算:如果i1 + i2可能会溢出,但你知道i3 - i1不会,那么i1 + i2 < i3可以回绕(如果它们是无符号整数)或者触发未定义的行为(如果它们是有符号的整数)或两者(如果你的系统恰好执行有符号整数溢出的回绕),而i3 - i1 > i2将不会有这个问题。


编辑添加:在评论中,你写“ len是来自buff的值,所以它可能是任何东西”。 在这种情况下,它们非常正确,并且p2 - p1 < len更安全,因为p1 + len可能无效。

“未定义的行为”适用于此处。 你不能比较两个指针,除非它们都指向同一个对象或指向该对象结束后的第一个元素。 这是一个例子:

 void func(int len) { char array[10]; char *p = &array[0], *q = &array[10]; if (p + len <= q) puts("OK"); } 

你可能会想到这样的function:

 // if (p + len <= q) // if (array + 0 + len <= array + 10) // if (0 + len <= 10) // if (len <= 10) void func(int len) { if (len <= 10) puts("OK"); } 

但是,编译器知道ptr <= q对于ptr所有有效值都为true,因此它可能会优化函数:

 void func(int len) { puts("OK"); } 

快多了! 但不是你想要的。

是的,有野外存在的编译器可以做到这一点。

结论

这是唯一安全的版本:减去指针并比较结果,不要比较指针。

 if (p - q <= 10) 

从技术上讲, p1p2必须是指向同一个数组的指针。 如果它们不在同一个数组中,则行为未定义。

对于添加版本, len的类型可以是任何整数类型。

对于差异版本,减法的结果是ptrdiff_t ,但任何整数类型都将被适当地转换。

在这些约束中,您可以以任何方式编写代码; 两者都不正确。 在某种程度上,这取决于你正在解决的问题。 如果问题是“数组的这两个元素是否比len元素分开”,那么减法是合适的。 如果问题是’ p2p1[len] (又名p1 + len )’相同的元素,则添加是合适的。

实际上,在许多具有统一地址空间的机器上,你可以减少指向不同数组的指针,但是你可能会得到一些有趣的效果。 例如,如果指针是某些结构类型的指针,而不是同一数组的部分,那么被视为字节地址的指针之间的差异可能不是结构大小的倍数。 这可能会导致特殊问题。 如果他们指向同一个数组,就不会有这样的问题 – 这就是限制到位的原因。

现有的答案显示了为什么if (p2-p1 > len)优于if (p1+len < p2) ,但仍然存在问题 - 如果p2碰巧指向缓冲区中的p1之前且len是无符号类型(例如size_t ),那么p2-p1将为负数,但会转换为大的无符号值以与unsigned len进行比较,因此结果可能为true,这可能不是您想要的。

所以你可能真的需要像if (p1 <= p2 && p2 - p1 > len)才能完全安全。

正如迪特里希已经说过的,比较不相关的指针是危险的,可以被认为是不确定的行为。

假设两个指针在0到2GB的范围内(在32位Windows系统上),减去2个指针将得到介于-2 ^ 31和+ 2 ^ 31之间的值。 这正是带符号的32位整数的域。 所以在这种情况下,减去两个指针似乎是有意义的,因为结果总是在你期望的域内。

但是,如果在您的可执行文件中启用了LargeAddressAware标志(这是特定于Windows的,不了解Unix),那么您的应用程序将具有3GB的地址空间(当在具有/ 3G标志的32位Windows中运行时)甚至4GB(在64位Windows系统上运行时)。 如果然后开始减去两个指针,结果可能会超出32位整数的域,并且您的比较将失败。

我认为这是地址空间最初划分为2GB的2个相等部分的原因之一,而LargeAddressAware标志仍然是可选的。 但是,我的印象是当前的软件(你自己的软件和你正在使用的DLL)似乎非常安全(没有人再减去指针,不是吗?)并且我自己的应用程序默认启用了LargeAddressAware标志。