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?
(&a + 1) == &b)
and(&b + 1) == &a)
? – Raglana
is the same as the address ofb
, or if one element past the address ofb
is the same as the address ofa
. The extra parenthesis in((&a)+1) == &b
aren't strictly needed. – Knivesaddr: a precedes b: 0 addr: b precedes a: 1
- a match. (GNU C11 (GCC) version 5.4.0 (i686-pc-cygwin) compiled by GNU C version 5.4.0, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3). IMO, your compiler is non-compliant. – Travesty&a, (&b)+1
, not thevoid*
converted values. – Travesty"something precedes something"
instructions are all optimized away to0
at compile time (lines 49 - 64 in the assembly). – Cataclinallea rax, [rbp-8]
,add rax, 4
,lea rdx, [rbp-4]
, after which it seems likerax
should be equal tordx
. However with-O3
it simply optimized both checks away. – Cataclinal0
is printed for all lines with optimization greater than 0. – Knivesa
andb
instead of of "just"int
. In this way the code fulfil the bolded part of point 6 (i.e. "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"). So the pointer should compare in one of the two cases. However, I also got the result that for-O0
one compare as expected. For-O1
(and higher) both fails to compare. I'm not a language lawyer but this seems a compiler bug to me. – Polymerizationp+1 == q
(when p,q are sequential) as much as must it be true? – Travestya
andb
the same type. Considering variant types opens up many more rabbit holes. – Travestyconst int *
,int *
,register int *
, etc. – Knivesa
andb
are not assigned. Does assigning them and using their values change the results?a=1; b = 2; .... return a + b;
? – Travestyuintptr_t
, while this one is regarding objects in the same translation unit. The answers there also don't reference the gcc bugzilla tickets discussing the issue. – Knives