4 ms·
It doesn't catch all signed overflow. If you define a function like this: int8_t abs8(int8_t n) { return (n >= 0) ? n : -n; } Even with `-ftrapv`, e
by trealira 3y ago
It doesn't catch all signed overflow. If you define a function like this:
int8_t abs8(int8_t n) {
return (n >= 0) ? n : -n;
}
Even with `-ftrapv`, evaluating abs8(-128) will produce -128, because 127 is the maximum value for a signed 8-bit integer (so trying to get 128 wraps around to -128), but this isn't caught. However, both Rust and Ada do catch this. This article highlights this difference between C and Ada:
https://borretti.me/article/signed-integers-asymmetrical https://borretti.me/article/signed-integers-asymmetrical
I know Rust catches it because after I read that article, I tested it with Rust in debug mode.
- jandrewrogers 3y agoI wasn’t necessarily assuming ‘-ftrapv’. For reliable software in C++ it is pretty common to have alternative integer type implementations that provide different fully defined behaviors and guarantees than the default integer types. This became legitimately transparent around C++17 IIRC. It is more or less drop-in and mostly produces optimal codegen. Also a good way to eliminate irritating integer behaviors inherited from C. It is easy enough to crank out custom integer types in C++ these days which are arbitrarily safe that there isn’t much excuse for not doing it, particularly since generics and metaprogramming does most of the work. Outside of interfacing with syscalls, there isn’t much use for C primitives beyond size_t.
- trealira 3y ago> For reliable software in C++ it is pretty common to have alternative integer type implementations that provide different fully defined behaviors and guarantees than the default integer types. This became legitimately transparent around C++17 IIRC. It is more or less drop-in and mostly produces optimal codegen. Also a good way to eliminate irritating integer behaviors inherited from C. I didn't know this. I can imagine you can create your own integer types as wrappers over existing primitives, and define them so that, e.g., on signed overflow, the program aborts or wraps (and your signed integers are actually a wrapper class over unsigned integers, but with operator overloading so that they act like signed integers). Is that what you mean? If not, could you show me what you mean?
- jandrewrogers 3y agoYes, this is one of the advantages of pervasive operator overloading in C++. You can create types that behave like primitive integral types for all practical purposes but which implement alternative behaviors like trapping overflow, blocking integer promotion, defining shift operators greater than or equal to the bit width, creating alternative casting rules, etc. However, it has not always been possible to implement this transparently in C++. This did not work well in older versions of C++ because the limited type inference meant that many common cases around different type interactions required explicit handling that made it clear you were not using native types. Around C++17 it became possible to define integer types that were almost entirely indistinguishable from the native types in ordinary C++ code. C++17 was important because it meant a lot of safety in the code base could become automagic via the type system, especially for primitive types. The code looks the same whether you are using it or not. That was a huge capability change. C++20 then generalized it to arbitrarily complex types. It is difficult to overstate how much this improves the conciseness and safety of non-trivial code.