Summary: | Invalid codegen when comparing pointer to one past the end and then dereferencing that pointer | ||
---|---|---|---|
Product: | gcc | Reporter: | Gabriel Ravier <gabravier> |
Component: | c | Assignee: | Not yet assigned to anyone <unassigned> |
Status: | RESOLVED DUPLICATE | ||
Severity: | normal | CC: | fw, gabravier, jason.e.cobb, johelegp, leni536, msebor |
Priority: | P3 | Keywords: | wrong-code |
Version: | 12.0 | ||
Target Milestone: | --- | ||
Host: | Target: | ||
Build: | Known to work: | ||
Known to fail: | Last reconfirmed: |
Description
Gabriel Ravier
2021-11-21 03:01:29 UTC
Dup of bug 61502. (or a dup of bug 93052 or many others). *** This bug has been marked as a duplicate of bug 61502 *** (In reply to Gabriel Ravier from comment #0) > PS: This also results in plenty of invalid warnings when compiling with > -Wall: > > <source>: In function 'f': > <source>:6:9: warning: array subscript 1 is outside array bounds of 'int[1]' > [-Warray-bounds] > 6 | *p = 2; > | ^~ > <source>:1:12: note: at offset 4 into object 'x' of size 4 > 1 | extern int x[1], y; > | ^ The warning in this case is valid and helpful: it's undefined to attempt to access an object using a pointer derived from a pointer to an unrelated object (the equality between pointers to adjacent objects notwithstanding). Well the code does not invoke undefined behavior here, it just so happens that `p == (x + 1)` because `y` happens to be laid out in memory after `x` (note: this isn't a guarantee, of course, but GCC can't prove this isn't the case as it's defined in another TU and it's quite easy to make this happen). The comparison doesn't imply the pointers have the same provenance, and the standard has a specific provision for this exact comparison: "If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object,72 the result of the comparison is unspecified." - [expr.eq] (https://eel.is/c++draft/expr.eq#3.1) Also, `y` isn't accessed through a pointer to `x`: I've already said the case where the function is incorrect is when `f` is called with `&y` as the first argument. If doing `p == (x + 1)` implied they derived from the same object, then that would imply after doing `&y == (x + 1)` doing `*&y` would invoke undefined behavior which is obviously ridiculous. Although there is a case to be made that this code is stupid and deserves a warning, though... I won't argue with that, this code is just something I wrote to test things after a 3 hour long conversation about DR 260 (<http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm>) and a lot of standardese lawyering, so it's not intended to be real life code. I'd say, though, that the warning is quite inaccurate in the details of what it's saying, as `p` isn't actually equivalent to `(x + 1)` just because `p == (x + 1)`. A complete program example: f.h: ``` #pragma once extern int x[1]; extern int y; int f(int* p, int* q); ``` f.cpp: ``` #include "f.h" int f(int* p, int* q) { *q = y; if (p == (x + 1)) { *p = 2; return y; } return 0; } ``` x_y.cpp: ``` #include "f.h" int y; int x[1]; ``` main.cpp: ``` #include "f.h" int main() { y=1; int i; return f(&y, &i); } ``` Compile with `g++ -o main main.cpp f.cpp x_y.cpp`. https://godbolt.org/z/G4KTKc7hE The well-formed program above has two possible evaluations, due to the unspecified comparison. In one evaluation `main` returns 0, in the other it returns 2. Compiled with g++ the program returns 1. Within the single invocation of `f` `p` is pointer to an object, namely `y`. Even after the unspecified comparison evaluates to true, `p` remains a pointer to `y`. Therefore dereferencing `p` is still valid in that branch. I don't think that it is a duplicate of bug 61502. The program does not rely on the object representation of the pointer objects, their printed value or their value converted to uintptr_t. The only thing that is questionable is the comparison with pointer past the end of an object, which is merely unspecified. |