简体   繁体   English

如何比较C指针?

[英]How to compare C pointers?

Recently, I wrote some code to compare pointers like this: 最近,我写了一些代码来比较像这样的指针:

if(p1+len < p2)

however, some staff said that I should write like this: 但是,有些工作人员说我应该这样写:

if(p2-p1 > len)

to be safe. 为了安全起见。 Here, p1 and p2 are char * pointers, len is an integer. 这里, p1p2char *指针, len是整数。 I have no idea about that.Is that right? 我对此一无所知。是吗?

EDIT1: of course, p1 and p2 pointer to the same memory object at begging. EDIT1:当然, p1p2在乞讨时指向同一个内存对象。

EDIT2:just one min ago,I found the bogo of this question in my code(about 3K lines),because len is so big that p1+len can't store in 4 bytes of pointer,so p1+len < p2 is true .But it shouldn't in fact,so I think we should compare pointers like this in some situation : EDIT2:就在一分钟之前,我在我的代码中找到了这个问题的bogo (约3K行),因为len太大了, p1+len不能存储在4个字节的指针中,所以p1 + len <p2但实际上它不应该,所以我认为我们应该在某些情况下比较像这样的指针:

if(p2 < p1 || (uint32_t)p2-p1 > (uint32_t)len)

In general, you can only safely compare pointers if they're both pointing to parts of the same memory object (or one position past the end of the object). 通常,只有指针指向同一个内存对象的部分(或者超过对象末尾的一个位置)时,才能安全地比较指针。 When p1 , p1 + len , and p2 all conform to this rule, both of your if -tests are equivalent, so you needn't worry. p1p1 + lenp2都符合此规则时,两个if -tests都是等效的,所以你不必担心。 On the other hand, if only p1 and p2 are known to conform to this rule, and p1 + len might be too far past the end, only if(p2-p1 > len) is safe. 另一方面,如果只知道p1p2符合这个规则,并且p1 + len可能在结束之后太远,那么只有if(p2-p1 > len)是安全的。 (But I can't imagine that's the case for you. I assume that p1 points to the beginning of some memory-block, and p1 + len points to the position after the end of it, right?) (但我无法想象你的情况。我假设p1指向某个内存块的开头,而p1 + len指向它结束后的位置,对吧?)

What they may have been thinking of is integer arithmetic: if it's possible that i1 + i2 will overflow, but you know that i3 - i1 will not, then i1 + i2 < i3 could either wrap around (if they're unsigned integers) or trigger undefined behavior (if they're signed integers) or both (if your system happens to perform wraparound for signed-integer overflow), whereas i3 - i1 > i2 will not have that problem. 他们可能一直在考虑的是整数运算:如果i1 + i2可能会溢出,但你知道i3 - i1不会,那么i1 + i2 < i3可以回绕(如果它们是无符号整数)或者触发未定义的行为(如果它们是有符号整数)或两者(如果你的系统恰好执行有符号整数溢出的环绕),而i3 - i1 > i2将不会有这个问题。


Edited to add: In a comment, you write " len is a value from buff, so it may be anything". 编辑添加:在评论中,你写“ len是来自buff的值,所以它可能是任何东西”。 In that case, they are quite right, and p2 - p1 > len is safer, since p1 + len may not be valid. 在这种情况下,它们是完全正确的,并且p2 - p1 > len更安全,因为p1 + len可能无效。

"Undefined behavior" applies here. “未定义的行为”适用于此处。 You cannot compare two pointers unless they both point to the same object or to the first element after the end of that object. 你不能比较两个指针,除非它们都指向同一个对象或指向该对象结束后的第一个元素。 Here is an example: 这是一个例子:

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

You might think about the function like this: 你可能会想到这样的功能:

// 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");
}

However, the compiler knows that ptr <= q is true for all valid values of ptr , so it might optimize the function to this: 但是,编译器知道ptr <= q对于ptr所有有效值都为真,因此它可能会优化函数:

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

Much faster! 快多了! But not what you intended. 但不是你想要的。

Yes, there are compilers that exist in the wild that do this. 是的,有野外存在的编译器可以做到这一点。

Conclusion 结论

This is the only safe version: subtract the pointers and compare the result, don't compare the pointers. 这是唯一安全的版本:减去指针并比较结果,不要比较指针。

if (p - q <= 10)

Technically, p1 and p2 must be pointers into the same array. 从技术上讲, p1p2必须是指向同一个数组的指针。 If they are not in the same array, the behaviour is undefined. 如果它们不在同一个数组中,则行为未定义。

For the addition version, the type of len can be any integer type. 对于加法版本, len的类型可以是任何整数类型。

For the difference version, the result of the subtraction is ptrdiff_t , but any integer type will be converted appropriately. 对于差异版本,减法的结果是ptrdiff_t ,但任何整数类型都将被适当地转换。

Within those constraints, you can write the code either way; 在这些约束中,您可以以任何方式编写代码; neither is more correct. 两者都不正确。 In part, it depends on what problem you're solving. 在某种程度上,这取决于你正在解决的问题。 If the question is 'are these two elements of the array more than len elements apart', then subtraction is appropriate. 如果问题是“数组的这两个元素是否比len元素分开”,则减法是合适的。 If the question is 'is p2 the same element as p1[len] (aka p1 + len )', then the addition is appropriate. 如果问题是' p2p1[len] (又名p1 + len )'相同,则添加是合适的。

In practice, on many machines with a uniform address space, you can get away with subtracting pointers to disparate arrays, but you might get some funny effects. 实际上,在许多具有统一地址空间的机器上,你可以减去指向不同数组的指针,但你可能会得到一些有趣的效果。 For example, if the pointers are pointers to some structure type, but not parts of the same array, then the difference between the pointers treated as byte addresses may not be a multiple of the structure size. 例如,如果指针是某些结构类型的指针,而不是同一数组的部分,那么被视为字节地址的指针之间的差异可能不是结构大小的倍数。 This may lead to peculiar problems. 这可能会导致特殊问题。 If they're pointers into the same array, there won't be a problem like that — that's why the restriction is in place. 如果他们指向相同的数组,就不会有这样的问题 - 这就是限制到位的原因。

The existing answers show why if (p2-p1 > len) is better than if (p1+len < p2) , but there's still a gotcha with it -- if p2 happens to point BEFORE p1 in the buffer and len is an unsigned type (such as size_t ), then p2-p1 will be negative, but will be converted to a large unsigned value for comparison with the unsigned len, so the result will probably be true, which may not be what you want. 现有的答案显示了为什么if (p2-p1 > len)优于if (p1+len < p2) ,但仍然存在问题 - 如果p2碰巧指向缓冲区中的p1 ,而len是无符号类型(例如size_t ),那么p2-p1将为负数,但会转换为大的无符号值,以便与unsigned len进行比较,因此结果可能为true,这可能不是您想要的。

So you might actually need something like if (p1 <= p2 && p2 - p1 > len) for full safety. 所以你可能真的需要像if (p1 <= p2 && p2 - p1 > len)才能完全安全。

As Dietrich already said, comparing unrelated pointers is dangerous, and could be considered as undefined behavior. 正如迪特里希已经说过的,比较不相关的指针是危险的,并且可以被认为是不确定的行为。

Given that two pointers are within the range 0 to 2GB (on a 32-bit Windows system), subtracting the 2 pointers will give you a value between -2^31 and +2^31. 假设两个指针在0到2GB的范围内(在32位Windows系统上),减去2个指针将得到介于-2 ^ 31和+ 2 ^ 31之间的值。 This is exactly the domain of a signed 32-bit integer. 这正是带符号的32位整数的域。 So in this case it does seem to make sense to subtract two pointers because the result will always be within the domain you would expect. 因此,在这种情况下,减去两个指针似乎是有意义的,因为结果将始终在您期望的域内。

However, if the LargeAddressAware flag is enabled in your executable (this is Windows-specific, don't know about Unix), then your application will have an address space of 3GB (when run in 32-bit Windows with the /3G flag) or even 4GB (when run on a 64-bit Windows system). 但是,如果在您的可执行文件中启用了LargeAddressAware标志(这是特定于Windows的,不了解Unix),那么您的应用程序将具有3GB的地址空间(当在具有/ 3G标志的32位Windows中运行时)甚至4GB(在64位Windows系统上运行时)。 If you then start to subtract two pointers, the result could be outside the domain of a 32-bit integer, and your comparison will fail. 如果然后开始减去两个指针,结果可能在32位整数的域之外,并且您的比较将失败。

I think this is one of the reasons why the address space was originally divided in 2 equal parts of 2GB, and the LargeAddressAware flag is still optional. 我认为这是地址空间最初划分为2GB的2个相等部分的原因之一,而LargeAddressAware标志仍然是可选的。 However, my impression is that current software (your own software and the DLL's you're using) seem to be quite safe (nobody subtracts pointers anymore, isn't it?) and my own application has the LargeAddressAware flag turned on by default. 但是,我的印象是当前的软件(你自己的软件和你正在使用的DLL)似乎非常安全(没有人再减去指针,不是吗?)并且我自己的应用程序默认启用了LargeAddressAware标志。

Neither variant is safe if an attacker controls your inputs 如果攻击者控制您的输入,则这两种变体都不安全

The expression p1 + len < p2 compiles down to something like p1 + sizeof(*p1)*len < p2 , and the scaling with the size of the pointed-to type can overflow your pointer: 表达式p1 + len < p2编译为p1 + sizeof(*p1)*len < p2 ,并且指向类型大小的缩放可能会溢出指针:

int *p1 = (int*)0xc0ffeec0ffee0000;
int *p2 = (int*)0xc0ffeec0ffee0400;
int len =       0x4000000000000000;
if(p1 + len < p2) {
    printf("pwnd!\n");
}

When len is multiplied by the size of int , it overflows to 0 so the condition is evaluated as if(p1 + 0 < p2) . len乘以int的大小时,它会溢出到0因此条件被评估为if(p1 + 0 < p2) This is obviously true, and the following code is executed with a much too high length value. 这显然是正确的,并且以下代码以太高的长度值执行。


Ok, so what about p2-p1 < len . 好的,那么p2-p1 < len Same thing, overflow kills you: 同样的事情,溢出会杀死你:

char *p1 = (char*)0xa123456789012345;
char *p2 = (char*)0x0123456789012345;
int len = 1;
if(p2-p1 < len) {
    printf("pwnd!\n");
}

In this case, the difference between the pointer is evaluated as p2-p1 = 0xa000000000000000 , which is interpreted as a negative signed value. 在这种情况下,指针之间的差异被评估为p2-p1 = 0xa000000000000000 ,这被解释为负的有符号值。 As such, it compares smaller then len , and the following code is executed with a much too low len value (or much too large pointer difference). 因此,它比len更小,并且执行以下代码时len值太低(或指针差异太大)。


The only approach that I know is safe in the presence of attacker-controlled values, is to use unsigned arithmetic: 我知道在存在攻击者控制值的情况下安全的唯一方法是使用无符号算法:

if(p1 < p2 &&
   ((uintptr_t)p2 - (uintptr_t)p1)/sizeof(*p1) < (uintptr_t)len
) {
    printf("safe\n");
}

The p1 < p2 guarantees that p2 - p1 cannot yield a genuinely negative value. p1 < p2保证p2 - p1不能产生真正的负值。 The second clause performs the actions of p2 - p1 < len while forcing use of unsigned arithmetic in a non-UB way. 第二个子句执行p2 - p1 < len的操作,同时强制以非UB方式使用无符号算术。 Ie (uintptr_t)p2 - (uintptr_t)p1 gives exactly the count of bytes between the bigger p2 and the smaller p1 , no matter the values involved. 即, (uintptr_t)p2 - (uintptr_t)p1精确地给出了较大的p2和较小的p1之间的字节数,无论所涉及的值如何。

Of course, you don't want to see such comparisons in your code unless you know that you need to defend against determined attackers. 当然,除非您知道需要为确定的攻击者辩护,否则您不希望在代码中看到此类比较。 Unfortunately, it's the only way to be safe, and if you rely on either form given in the question, you open yourself up to attacks. 不幸的是,这是保证安全的唯一方法,如果您依赖于问题中给出的任何一种形式,那么您就会受到攻击。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM