5 ms·
Checked integer arithmetic in the prospect of C23
- ash_gti 4y agoI know most of these are compiler intrinsically but it’s good to have them standardized.
- Someone 4y agoFTA: “Their working is quite simple: the arithmetic is as if performed in the set of mathematical integers and then the value is written to result. If it fits, the return value is false. If it doesn’t fit, the return value is true”* They also give example code bool add_invalid = ckd_add(&result_add, a, b); I can see that fits with “most of the time, anything positive means ‘no error’”, for example in malloc, write, read or printf, but these new functions return bool, not int, and the chosen method will require writing a double negation sometimes: if(!add_invalid) { … } That’s not too bad, but if I were to see if(!ckd_add(&result_add, a, b)) { … } I would expect that to test for failure, not success. Because of that, I think I would have chosen to return true on success, false on failure. I’m curious as to what arguments led to the choice made.
- chongli 4y agoIn C, the value 0 is equivalent to false and all nonzero values are equivalent to true. It’s a convention throughout the C standard library to return 0 on success and nonzero when some error occurred. The behaviour of the new checked arithmetic library is consistent with that convention.
- Someone 4y ago> It’s a convention throughout the C standard library to return 0 on success and nonzero when some error occurred. If only it were so simple. read and write, for example, return a number less than zero on error and a non-negative number on success, and malloc returns zero on error, and nonzero on success. The general rule for early C seems to be “whatever’s the best way to cram a return value or an error in an int” (probably the correct decision for the time) Also, these new functions return a bool, which, in C23, gets integer-converted to zero for false and one for true (https://en.cppreference.com/w/c/language/bool_constant https://en.cppreference.com/w/c/language/bool_constant. C17 had macros for true and false, with false being zero) and the reverse, converting to bool similarly has zero fro false (https://en.cppreference.com/w/c/language/conversion#Boolean_conversion https://en.cppreference.com/w/c/language/conversion#Boolean_...): “A value of any scalar type (including nullptr_t) (since C23) can be implicitly converted to _Bool. The values that compare equal to an integer constant expression of value zero are converted to 0 (false), all other values are converted to 1 (true).” (https://en.cppreference.com/w/c/language/bool_constant https://en.cppreference.com/w/c/language/bool_constant)
- dahfizz 4y ago_when the return value is an error code_, zero means success and nonzero means failure. Functions like `read`, `recv`, etc etc don't just return an error code. They return an actual value. Functions that only return an error code like `stat`, `connect`, and the proposed ckd_add, return 0 on success and nonzero on error.
- SAI_Peregrinus 4y agoAnd in shell it's convention for programs to return 0 for success and nonzero when an error occurred. The issue is that in shell, the `true` builtin returns `0` and `false` returns `1`, which is the opposite of C's `bool`. And almost every other language's Boolean type.
- gustedt 4y agoUnfortunately there are several error conventions in the C standard. Here, the committee just standardized existing practice, namely the gcc builtins. We just adjusted the call sequence in putting the pointer parameter for the result first.
- dooglius 4y agoI find the most readable, unambiguous thing is to define explicit macros or constants, e.g. if(ckd_add(&result_add, a, b) == CKD_SUCCESS) { ... } or alternatively, if(CKD_SUCCESS(ckd_add(&result_add, a, b))) { ... }
- kevin_thibedeau 4y agoThe return value is the overflow condition so just name it CKD_OVERFLOW.
- dahfizz 4y agoThe proposal fits my intuition. When the return value is an error code, truthy values are an error. int rc = func(); if( rc ) { /*handle error*/ } examples from the stdlib: connect(), stat(), etc. Hell, even main is defined to return 0 on success, nonzero on error.
- RustyRussell 4y agoNot for bool though.
- RustyRussell 4y agoYes, it's backwards. And counter-intuitive use of bool :( They felt fine changing the argument order, why stick with the reverse polarity?
- raphlinus 4y agoI posted overflow checking of signed integer arithmetic as a puzzle yesterday[1]. I got some good responses but none quite as minimal wrt number of instructions as my own solution: bool add_will_overflow(int32_t a, int32_t b) { uint32_t c = (uint32_t)a + (uint32_t)b; return (((uint32_t)a ^ c) & ((uint32_t)b ^ c)) >> 31; } That produces the following assembly (see Godbolt[2]): lea edx, [rdi+rsi] mov eax, edi xor eax, edx xor esi, edx and eax, esi shr eax, 31 ret In Rust, you can write a.checked_add(b).is_none() which produces the following assembly[3]: add edi, esi seto al ret A fun fact about this code: the overflow flag which is set by the add instruction and then harvested dates back at least to the 8080 (almost 50 years ago) and is not present in vanilla ARM. However, Apple Silicon has it as an extension, to make life easier for Rosetta 2 binary translation[4]. So when you do get to use this shorter code sequence, be thankful of the effort that chip designers put in to make it execute efficiently. I expect the C23 built-in functions will perform as well as Rust here, which is a win both for ergonomics (you can't really consider the current state of "will a+b overflow" to be discoverable) and performance. [1]: https://mastodon.online/@raph/109535617953722719 https://mastodon.online/@raph/109535617953722719 [2]: https://godbolt.org/z/17zMsWjYv https://godbolt.org/z/17zMsWjYv [3]: https://rust.godbolt.org/z/36Ta9oP1P https://rust.godbolt.org/z/36Ta9oP1P [4]: https://news.ycombinator.com/item?id=33635720 https://news.ycombinator.com/item?id=33635720
- dooglius 4y agoThis is already present as a builtin in in GNU C (as indicated in TFA) and it already results in the optimal code: https://godbolt.org/z/qc4zvav7E https://godbolt.org/z/qc4zvav7E
- tinglymintyfrsh 4y agoAlso, Hacker's Delight and OpenBSD probably have clever solutions for these.
- wahern 4y agoSurprisingly, OpenBSD does not have a library (neither a public API nor even just routines which are copied project-to-project as is common with OpenBSD utilities and daemons) to handle arithmetic overflow. The closest might be malloc/realloc extensions, like reallocarray, that handle common scenarios where arithmetic overflow is seen.
- jacquesm 4y agoWhat was the rationale for not simply making it optional to throw an exception on overflow?
- dahfizz 4y agoC doesn't have exceptions...?
- heywhatupboys 4y agokinda does though. floating point exception vectors are a thing and settable from most compiler impls
- dahfizz 4y agofenv is available in standard C99. There is a GNU extension to trap and throw a signal when a fp error is hit (feenableexcept). I would definitely argue that signals != exceptions.
- jacquesm 4y agoThe floating point implementation can signal SIGFPE, with a code FPE_INTOVF, it would seem to me that that is a suitable exception mechanism, it's just that the source isn't the floating point unit but the regular CPU. Signals (kill), signal, raise, sigsetjmp, siglongjmp etc are C's exception handling mechanism. It's not as well integrated into the language as say a try-catch construct but it works well enough for situations like these. See: signal.h and setjmp.h https://en.wikipedia.org/wiki/Signal_(IPC) https://en.wikipedia.org/wiki/Signal_(IPC)
- dahfizz 4y agoSignals are not exceptions. Languages with exceptions still have to handle signals separately. Signals are an OS construct, and exceptions are a language construct. To the question of "why not raise a signal on integer overflow in C?" - because signals are a terrible way of dealing with this. The signal handler doesn't know what code caused the overflow, and can't really do anything about it. Once the signal handler returns, the code itself has no idea it caused an overflow. Signals are a way for the OS to send signals to your program and not for control flow, after all. That's why `feenableexcept` is a niche extension that nobody uses. The standard way of checking for fp errors is by calling `fetestexcept`. Personally I prefer this strategy (doing operation, then checking for errors) vs the new proposal for ints (checking for potential errors before doing the operation). But that is a matter of taste.
- RcouF1uZ4gsC 4y agoOne major concern with these type of safety functions is that you have to explicitly opt in. If you are actually thinking about the need to call these function, you are already thinking about overflow.
- jdhdjdbdjdbd 4y agoAnd that's why you use C in the first place?
- jacquesm 4y ago> If you are actually thinking about the need to call these function, you are already thinking about overflow. As you should, for any datatype.
- deleted 4y ago[deleted]
- tinglymintyfrsh 4y agotl;dr #include <stdckdint.h> bool ckd_add(type1 *result, type2 a, type3 b); bool ckd_sub(type1 *result, type2 a, type3 b); bool ckd_mul(type1 *result, type2 a, type3 b); #include <stdckdint.h> #include <limits.h> /* ... */ int x; int a = INT_MAX; int b = INT_MAX; if (!chk_add(&x, a, b)) { /* error! */ } Other stuff on the table for C23 - https://thephd.dev/c-the-improvements-june-september-virtual-c-meeting https://thephd.dev/c-the-improvements-june-september-virtual... - (PDF) https://open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf https://open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf
- heywhatupboys 4y ago> if (!chk_add(&x, a, b)) { > /* error! */ > } does it return non-zero on success???
- EdSchouten 4y agoIt returns a boolean.
- tinglymintyfrsh 4y agoThey're using a bool, so it violates the old-school C paradigm used for library calls. This is more of a macro rather than a syscall or standard library function call.
- comex 4y ago
- olliej 4y agoThis is simply standard using the builtins that the major c++ compilers already have. It does not remove the absurd “overflow is UB” semantics that introduces security bugs.
- unwind 4y agoReally fun, great introduction to the exciting new features. Which part of the example implemented using `nullptr` instead of `NULL`, which is also from the future, though?
- gustedt 4y agoYes, `nullptr` will also be in C23.
- pwdisswordfish9 4y agoWhat for, if ((void *)0) is sufficient in C?