Reputation: 3682
GCC warns of out-of-bounds array access only when used in a static_cast
even with most of the warning options enabled.
The sample code (live):
#include <iterator>
#include <numeric>
#include <print>
int main()
{
int arr[ 5 ] { };
std::iota( std::begin( arr ), std::end( arr ), 1 );
// <no warning> -<warning>-----
std::println( "{} at address: {:p}", *(arr - 1), static_cast<const void*>(&( *(arr - 1) )) );
// <no warning> -<warning>-
std::println( "{} at address: {:p}", arr[ -1 ], static_cast<const void*>(&arr[ -1 ]) );
int* const ptr { arr + 2 };
// <no warning> -<warning>-
std::println( "{} at address: {:p}", ptr[ -3 ], static_cast<const void*>(&ptr[ -3 ]) );
}
The compiler output:
<source>: In function 'int main()':
<source>:12:54: warning: array subscript -1 is outside array bounds of 'int [5]' [-Warray-bounds=]
12 | std::println( "{} at address: {:p}", *(arr - 1), static_cast<const void*>(&( *(arr - 1) )) );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:8:9: note: at offset -4 into object 'arr' of size 20
8 | int arr[ 5 ] { };
| ^~~
<source>:14:78: warning: array subscript -1 is below array bounds of 'int [5]' [-Warray-bounds=]
14 | std::println( "{} at address: {:p}", arr[ -1 ], static_cast<const void*>(&arr[ -1 ]) );
| ^~~~~~~~~~
<source>:8:9: note: while referencing 'arr'
8 | int arr[ 5 ] { };
| ^~~
<source>:18:53: warning: array subscript -1 is outside array bounds of 'int [5]' [-Warray-bounds=]
18 | std::println( "{} at address: {:p}", ptr[ -3 ], static_cast<const void*>(&ptr[ -3 ]) );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:8:9: note: at offset -4 into object 'arr' of size 20
8 | int arr[ 5 ] { };
| ^~~
For instance, I naturally expect to see a warning for *(arr - 1)
too. But apparently, only the expressions in the static_cast
s (e.g. &( *(arr - 1) )
) are pedantically checked for the presence of invalid accesses.
Is this a bug in GCC? Because the code is pretty trivial and explicit. I expect a compiler to display warnings at least in such simple scenarios.
Note: Clang is even worse and only warns about arr[ -1 ]
.
Upvotes: 1
Views: 330
Reputation: 1
Ensuring that array indices are within the valid range would solve this
int main()
{
int arr[ 5 ] { };
std::iota( std::begin( arr ), std::end( arr ), 1 );
// The valid indices for arr are 0 to 4. Negative indices are out of bounds.
// We can't fix this by changing the index because we don't know what the intended behavior was.
// So, we will comment out these lines.
// std::println( "{} at address: {:p}", *(arr - 1), static_cast<const void*>(&( *(arr - 1) )) );
// std::println( "{} at address: {:p}", arr[ -1 ], static_cast<const void*>(&arr[ -1 ]) );
int* const ptr { arr + 2 };
// The valid indices for ptr are -2 to 2 because ptr points to the middle of arr.
// -3 is out of bounds. We can't fix this by changing the index because we don't know what the intended behavior was.
// So, we will comment out this line.
// std::println( "{} at address: {:p}", ptr[ -3 ], static_cast<const void*>(&ptr[ -3 ]) );
}```
Upvotes: 0
Reputation: 93556
These warnings you will only occur when optimisation is enabled. The abstract interpretation that occurs in the optimiser allow it to detect some potential runtime errors at compile time. They are opportunistic warnings enabled by the way the optimiser works - you cannot expect the same results across all compilers, or even at different optimisation settings.
In this case you are passing the expressions as variadic arguments so although you have cast to a const pointer, println()
makes no promises to respect that - the variadic arguments have no specific type defined compile time. So the compiler sees that you are passing a const pointer with no idea how the receiving function is going to use it, so there is a potential for an out of bounds write access.
It is not the static_cast
that causes this, it is the taking of address &
. The cast is necessary to comply with the format specifier. I suspect if the compiler were not format specifier aware (which it need not be), you would get the warning without the cast.
In the case of the expression *(arr-1)
that is a read access and pass by value (not a pointer) and although you may not get a useful result, you are not modifying the out-of-bounds location, so there is noting to warn about.
You can simplify the test to demonstrate the behaviour is nothing to do with the static cast and everything to do with writing or potentially writing out-of-bounds using the following expressions:
int x = *(arr-1) ; // No warning - out of bounds read
*(arr-1) = 0 ; // warning: array subscript 4611686018427387903 is above array bounds of 'int [5]'
int* p = &(*(arr-1)) ; // no warning, juts taken the address, not written to.
*p = 2 ; // warning: array subscript -1 is outside array bounds of 'int [5]'
Upvotes: 1