When having this code compiling with -Warray-bounds
. I get a warning when declaring array2 array index 3 is past the end of the array (which contains 3 elements)
. But not when declaring array1 even though it has to be the same type thus carrying the same size information. Is this a bug in clang?
enum class Format : int {
Off = 55,
FormatA = 66,
FormatB = 77,
};
inline Format (&AllFormats())[3] {
static Format values[] = {
Format::Off,
Format::FormatA,
Format::FormatB
};
return values;
}
int main()
{
auto array1 = AllFormats();
auto v3 = array1[3];
Format (&array2)[3] = AllFormats();
v3 = array2[3];
}
This is due to the fact that C++ does not do bounds checking. Languages like Java and python have bounds checking so if you try to access an out of bounds element, they throw an error. C++ design principle was that it shouldn't be slower than the equivalent C code, and C doesn't do array bounds checking.
Array bound checking refers to determining whether all array references in a program are within their declared ranges. This checking is critical for software verification and validation because subscripting arrays beyond their declared sizes may produce unexpected results, security holes, or failures.
Abstract: As a type-safe program language, Java requires bounds checks of array accesses. Whenever an array element is accessed, a cmp (compare) instruction is executed to check whether the index value is within the valid bounds. Array bounds checks may prevent many useful optimizations because of precise exception.
Note that C and C++ do not do bounds checking on arrays, so stuff like that isn't going to be caught at compile or run time. No, Undefined behavior "works in your favor" when it crashes cleanly.
even though it has to be the same type
You’d think that. But if you check, you’ll find that they actually don’t have the same type:
std::cout << typeid(array1).name() << "\n";
std::cout << typeid(array2).name() << "\n";
P6Format
A3_6Format
Oops. The array returned by AllFormats
decays to a pointer when assigned to an auto
variable because that’s how the type deduction rules for auto
work. Compare:
int& foo() {
static int x = 42;
return x;
}
auto x = foo(); // Type of `x` is `int`, not `int&`.
To prevent this, declare array1
as auto&
or auto&&
.
But not at line 16 even though it has to be the same type
Assuming by it you refer to auto array1 = AllFormats()
, then it doesn't have the same type. auto
is never deduced to be a reference, so array1
is not a reference. It is a non-reference, and is deduced to be the decayed result, i.e. a pointer to Format
.
As the pointer type doesn't carry information about the size of the pointed array, the compiler wasn't able to prove that the subscript operator overflows the array.
To declare a reference, you can use either:
auto& array1 = AllFormats(); // 1.
auto&& array1 = AllFormats(); // 2.
decltype(auto) array1 = AllFormats(); // 3.
AllFormats
returns an lvalue reference. It would be an rvalue reference if AllFormats
returned Format&&
.auto
type deduction uses different rules from decltype
deduction. One key difference is that auto
is never a reference, while decltype(E);
may be a reference, depending on the expression E
. decltype(auto) var = E
allows a declaration using the decltype
rules as if decltype(E)
was used.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With