Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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\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?

like image 977
dbush Avatar asked Aug 30 '17 17:08

dbush


People also ask

Can you compare pointers of different types?

When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. If two pointers to object types both point to the same object, or both point one past the last element of the same array object, they compare equal.

What operator is used to compare two pointers if they are not equal?

Equality operator (==,!=) Pointers of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.

How do you compare two pointer variables?

How to compare pointers in C/C++? We can compare pointers if they are pointing to the same array. Relational pointers can be used to compare two pointers. Pointers can't be multiplied or divided.


1 Answers

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.)

like image 52
Keith Thompson Avatar answered Sep 24 '22 01:09

Keith Thompson