无关指针的相等性比较是否可以评估为true? [英] Can an equality comparison of unrelated pointers evaluate to true?

查看:78
本文介绍了无关指针的相等性比较是否可以评估为true?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C标准的第6.5.9节 C标准==和!=运算符的a>指出以下内容:

2 下列条件之一应成立:

  • 两个操作数均具有算术类型;
  • 两个操作数都是指向兼容类型的合格或不合格版本的指针;
  • 一个操作数是一个指向对象类型的指针,另一个是一个指向void的合格或不合格版本的指针;或
  • 一个操作数是一个指针,另一个是空指针常量.

...

6 当且仅当都是空指针时,两个指针比较相等, 两者都是指向同一对象的指针(包括指向对象的指针) 以及子对象的开头)或函数,它们都是指向的指针 一个数组对象的最后一个元素之后,或者一个是 指向一个数组对象末尾的指针,另一个是 指向发生的另一个数组对象的开始的指针 立即跟随地址空间中的第一个数组对象. 109)

7 对于这些运算符,指向对象的指针 不是数组的元素的行为与指向第一个的指针的行为相同 长度为1且对象类型为其数组的元素 元素类型.

脚注109:

109)两个对象可能在内存中相邻,因为它们相邻 较大数组的元素或结构的相邻成员而没有 在它们之间进行填充,或者因为实施选择了放置 即使它们是无关的.如果先前无效的指针 操作(例如在数组范围之外的访问)产生的未定义 行为,随后的比较也会产生不确定的行为.

这似乎表明您可以执行以下操作:

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

这应该是合法的,因为我们在数组的末尾使用了一个地址元素(在本例中,该元素是一个大小为1的数组),而没有取消引用.更重要的是,如果在内存中一个变量紧随另一个变量之后,则需要这两个语句之一来输出1.

但是,测试似乎并没有解决这个问题.给出以下测试程序:

#include <stdio.h>

struct s {
    int a;
    int b;
};

int main()
{
    int a;
    int b;
    int *x = &a;
    int *y = &b;

    printf("sizeof(int)=%zu\n", sizeof(int));
    printf("&a=%p\n", (void *)&a);
    printf("&b=%p\n", (void *)&b);
    printf("x=%p\n", (void *)x);
    printf("y=%p\n", (void *)y);

    printf("addr: a precedes b: %d\n", ((&a)+1) == &b);
    printf("addr: b precedes a: %d\n", &a == ((&b)+1));
    printf("pntr: a precedes b: %d\n", (x+1) == y);
    printf("pntr: b precedes a: %d\n", x == (y+1));

    printf("  x=%p,   &a=%p\n", (void *)(x), (void *)(&a));
    printf("y+1=%p, &b+1=%p\n", (void *)(y+1), (void *)(&b+1));

    struct s s1;
    x=&s1.a;
    y=&s1.b;
    printf("addr: s.a precedes s.b: %d\n", ((&s1.a)+1) == &s1.b);
    printf("pntr: s.a precedes s.b: %d\n", (x+1) == y);
    return 0;
}

编译器是gcc 4.8.5,系统是CentOS 7.2 x64.

使用-O0,我得到以下输出:

 sizeof(int)=4
&a=0x7ffe9498183c
&b=0x7ffe94981838
x=0x7ffe9498183c
y=0x7ffe94981838
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 1
  x=0x7ffe9498183c,   &a=0x7ffe9498183c
y+1=0x7ffe9498183c, &b+1=0x7ffe9498183c
addr: s.a precedes s.b: 1
 

我们在这里可以看到int是4个字节,a的地址比b的地址高4个字节,并且x保留了a的地址,而y保存b的地址.但是,比较&a == ((&b)+1)的评估结果为false,而比较(x+1) == y的评估结果为true.我希望两者都是正确的,因为要比较的地址看起来相同.

有了-O1,我得到了:

 sizeof(int)=4
&a=0x7ffca96e30ec
&b=0x7ffca96e30e8
x=0x7ffca96e30ec
y=0x7ffca96e30e8
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 0
  x=0x7ffca96e30ec,   &a=0x7ffca96e30ec
y+1=0x7ffca96e30ec, &b+1=0x7ffca96e30ec
addr: s.a precedes s.b: 1
pntr: s.a precedes s.b: 1
 

现在,即使(与以前一样)被比较的地址似乎相同,两个比较的结果都为假.

这似乎指向不确定的行为,但是基于我阅读以上段落的方式似乎应该允许.

还请注意,在所有情况下,对struct中相同类型的相邻对象的地址进行比较都会打印出预期的结果.

我在这里误读了有关允许的内容(这是UB)的信息,还是这种情况下的gcc版本不符合要求?

解决方案

不相关指针的相等性比较能否得出true?

是,但是...

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

根据我对C标准的解释,有三种可能性:

  • a紧接b
  • b紧接a
  • a和b都不能紧接在另一个之前(它们之间可能有缝隙或其他物体)

我前段时间对此进行了研究,得出的结论是,GCC在==运算符上对指针执行了无效的优化,即使地址相同,它也会产生false,因此我提交了一个错误报告:

https://gcc.gnu.org/bugzilla/show_bug.cgi? id = 63611

该错误已作为另一个报告的副本被关闭:

https://gcc.gnu.org/bugzilla/show_bug.cgi? id = 61502

对这些错误报告做出响应的GCC维护者似乎认为,两个对象的邻接关系不需要一致,并且它们地址的比较可能表明它们在该程序的同一运行中是否相邻. .从我对第二个Bugzilla票证的评论中可以看出,我非常不同意.我认为,没有==运算符的一致行为,该标准对相邻对象的要求就没有意义,我认为我们必须假设这些词不仅仅是装饰性的.

这是一个简单的测试程序:

#include <stdio.h>
int main(void) {
    int x;
    int y;
    printf("&x = %p\n&y = %p\n", (void*)&x, (void*)&y);
    if (&y == &x + 1) {
        puts("y immediately follows x");
    }
    else if (&x == &y + 1) {
        puts("x immediately follows y");
    }
    else {
        puts("x and y are not adjacent");
    }
}

当我使用GCC 6.2.0进行编译时,在所有优化级别上,xy的打印地址都恰好相差4个字节,但是我只能在-O0上得到y immediately follows x.在-O1-O2-O3处,我得到x and y are not adjacent.我相信这是不正确的行为,但显然不会得到解决.

我认为

clang 3.8.1的行为正确,在所有优化级别上均显示x immediately follows y. Clang以前对此有问题;我报告了:

https://bugs.llvm.org/show_bug.cgi?id=21327

它已得到纠正.

我建议不要依赖行为可能一致的相邻对象的地址比较.

(请注意,指向不相关对象的指针上的关系运算符(<<=>>=)具有未定义的行为,但是通常需要使用相等运算符(==!=)表现一致.)

Section 6.5.9 of the C standard regarding the == and != operators states the following:

2 One of the following shall hold:

  • both operands have arithmetic type;
  • both operands are pointers to qualified or unqualified versions of compatible types;
  • one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void; or
  • one operand is a pointer and the other is a null pointer constant.

...

6 Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.109)

7 For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

Footnote 109:

109) Two objects may be adjacent in memory because they are adjacent elements of a larger array or adjacent members of a structure with no padding between them, or because the implementation chose to place them so, even though they are unrelated. If prior invalid pointer operations (such as accesses outside array bounds) produced undefined behavior, subsequent comparisons also produce undefined behavior.

This would seem to indicate you could do the following:

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

This should be legal since we are using an address one element past the end of an array (which in this case is a single object treated as an array of size 1) without dereferencing it. More importantly, one of these two statements would be required to output 1 if one variable immediately followed the other in memory.

However, testing didn't seem to pan this out. Given the following test program:

#include <stdio.h>

struct s {
    int a;
    int b;
};

int main()
{
    int a;
    int b;
    int *x = &a;
    int *y = &b;

    printf("sizeof(int)=%zu\n", sizeof(int));
    printf("&a=%p\n", (void *)&a);
    printf("&b=%p\n", (void *)&b);
    printf("x=%p\n", (void *)x);
    printf("y=%p\n", (void *)y);

    printf("addr: a precedes b: %d\n", ((&a)+1) == &b);
    printf("addr: b precedes a: %d\n", &a == ((&b)+1));
    printf("pntr: a precedes b: %d\n", (x+1) == y);
    printf("pntr: b precedes a: %d\n", x == (y+1));

    printf("  x=%p,   &a=%p\n", (void *)(x), (void *)(&a));
    printf("y+1=%p, &b+1=%p\n", (void *)(y+1), (void *)(&b+1));

    struct s s1;
    x=&s1.a;
    y=&s1.b;
    printf("addr: s.a precedes s.b: %d\n", ((&s1.a)+1) == &s1.b);
    printf("pntr: s.a precedes s.b: %d\n", (x+1) == y);
    return 0;
}

Compiler is gcc 4.8.5, system is CentOS 7.2 x64.

With -O0, I get the following output:

sizeof(int)=4
&a=0x7ffe9498183c
&b=0x7ffe94981838
x=0x7ffe9498183c
y=0x7ffe94981838
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 1
  x=0x7ffe9498183c,   &a=0x7ffe9498183c
y+1=0x7ffe9498183c, &b+1=0x7ffe9498183c
addr: s.a precedes s.b: 1

We can see here that an int is 4 bytes and that the address of a is 4 bytes past the address of b, and that x holds the address of a while y holds the address of b. However the comparison &a == ((&b)+1) evaluates to false while the comparison (x+1) == y evaluates to true. I would expect both to be true as the addresses being compared appear identical.

With -O1, I get this:

sizeof(int)=4
&a=0x7ffca96e30ec
&b=0x7ffca96e30e8
x=0x7ffca96e30ec
y=0x7ffca96e30e8
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 0
  x=0x7ffca96e30ec,   &a=0x7ffca96e30ec
y+1=0x7ffca96e30ec, &b+1=0x7ffca96e30ec
addr: s.a precedes s.b: 1
pntr: s.a precedes s.b: 1

Now both comparisons evaluate to false even though (as before) the address being compared appear to be the same.

This seems to point to undefined behavior, but based on how I read the above passage it seems this should be allowed.

Note also that the comparison of the addresses of adjacent objects of the same type in a struct prints the expected result in all cases.

Am I misreading something here regarding what is allowed (meaning this is UB), or is this version of gcc non-conforming in this case?

解决方案

Can an equality comparison of unrelated pointers evaluate to true?

Yes, but ...

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

There are, by my interpretation of the C standard, three possibilities:

  • a immediately precedes b
  • b immediately precedes a
  • neither a nor b immediately precedes the other (there could be a gap, or another object, between them)

I played around with this some time ago and concluded that GCC was performing an invalid optimization on the == operator for pointers, making it yield false even when the addresses are the same, so I submitted a bug report:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63611

That bug was closed as a duplicate of another report:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61502

The GCC maintainers who responded to these bug reports seem to be of the opinion that adjacency of two objects need not be consistent and that the comparison of their addresses might show them to be adjacent or not, within the same run of the program. As you can see from my comments on the second Bugzilla ticket, I strongly disagree. In my opinion, without consistent behavior of the == operator, the standard's requirements for adjacent objects is meaningless, and I think we have to assume that those words are not merely decorative.

Here's a simple test program:

#include <stdio.h>
int main(void) {
    int x;
    int y;
    printf("&x = %p\n&y = %p\n", (void*)&x, (void*)&y);
    if (&y == &x + 1) {
        puts("y immediately follows x");
    }
    else if (&x == &y + 1) {
        puts("x immediately follows y");
    }
    else {
        puts("x and y are not adjacent");
    }
}

When I compile it with GCC 6.2.0, the printed addresses of x and y differ by exactly 4 bytes at all optimization levels, but I get y immediately follows x only at -O0; at -O1, -O2, and -O3 I get x and y are not adjacent. I believe this is incorrect behavior, but apparently, it's not going to be fixed.

clang 3.8.1, in my opinion, behaves correctly, showing x immediately follows y at all optimization levels. Clang previously had a problem with this; I reported it:

https://bugs.llvm.org/show_bug.cgi?id=21327

and it was corrected.

I suggest not relying on comparisons of addresses of possibly adjacent objects behaving consistently.

(Note that relational operators (<, <=, >, >=) on pointers to unrelated objects have undefined behavior, but equality operators (==, !=) are generally required to behave consistently.)

这篇关于无关指针的相等性比较是否可以评估为true?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆