Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
262 views
in Technique[技术] by (71.8m points)

c - Can an equality comparison of unrelated pointers evaluate to true?

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
", (&a + 1) == &b);
printf("b precedes a: %d
", (&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
", sizeof(int));
    printf("&a=%p
", (void *)&a);
    printf("&b=%p
", (void *)&b);
    printf("x=%p
", (void *)x);
    printf("y=%p
", (void *)y);

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

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

    struct s s1;
    x=&s1.a;
    y=&s1.b;
    printf("addr: s.a precedes s.b: %d
", ((&s1.a)+1) == &s1.b);
    printf("pntr: s.a precedes s.b: %d
", (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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Can an equality comparison of unrelated pointers evaluate to true?

Yes, but ...

int a;
int b;
printf("a precedes b: %d
", (&a + 1) == &b);
printf("b precedes a: %d
", (&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
&y = %p
", (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.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...