Is it safe to let integer variable overflow? - Page 1
Forum section: electronics / beginners

author: Veketti
author_rank: Regular Contributor
timestamp: December 26, 2023, 01:31:26 pm
content: Dear All,So the question is about counters in C -language and embedded programming whether that makes any differences. Here's two different scenarios to count from 0 to 29 and repeat it indefinitely.Code:uint8_t counter = 0;while(1){// Is this safe to use?printf("Number is: %d \n", counter);counter = (counter+1) % 30;// Or need to zero counter manually?printf("Number is: %d \n", counter);counter = counter+1;if (counter > (30-1) ) counter = 0;delay(200);}The question is, is it safe to use the modulus method? Counter overflows after 255 -> 0, but does it ever cause an issue?

author: wofritz
author_rank: Contributor
timestamp: December 26, 2023, 01:53:37 pm
content: Since you assign the result of the mod operation back to the counter variable, it will never overflow.

author: radiolistener
author_rank: Super Contributor
timestamp: December 26, 2023, 01:54:13 pm
content: it is safe, because there is no overflow, the counter never reach value above 30.Overflow for 8-bit unsigned type will happens at 255->256.Overflow is normal behavior and you can use it, but you're needs to understand how it works...

author: Faranight
author_rank: Unknown
timestamp: December 26, 2023, 02:00:18 pm
content: Modulus should be fine, I use it all the time in my code to set the upper limits for counters. I reckon the difference is how the code compiles down to assembly - the "if" method could result in cmp/jmp instructions or their equivalents while the modulo likely doesn't (the latter is preferred in cryptography due to offering constant execution times to avoid side-channel attacks). Just be careful when setting the limit near the variable type limit (255 for uint8_t, 65535 for uint16_t, etc) to avoid overflows. If the variable limit happens to be your counter upper limit then you can also avoid using modulo and have the counter overflow "naturally".

author: Siwastaja
author_rank: Super Contributor
timestamp: December 26, 2023, 02:08:47 pm
content: Are you asking about if "counter+1" part overflows? If yes, the answer is, it does not because integer promotion rule promotes this uint8_t counter toint, which, by C standard, is at least 16 bits wide. But it is better to be explicit with a cast: ((uint16_t)counter + 1)%30. Then it is obvious for us human readers that counter+1 cannot overflow because counter is only uint8_t and the cast increases the range way more than needed for the +1 operation (uint16_t can hold 65535, so it sure can (255+1)), without having to remember the integer promotion rules.

author: Veketti
author_rank: Regular Contributor
timestamp: December 26, 2023, 02:44:05 pm
content: Ah, yes. What a brainfart. Of course it doesn't overflow as it gets assigned the mod value.. But in case let's assume it didn't get assigned and keeps increasing until it overflows, it's safe right? I can't find it now, but I remember read that in case of unsigned the result is safe to overflow, but in case signed, behaviour is not guaranteed?

author: T3sl4co1l
author_rank: Super Contributor
timestamp: December 26, 2023, 03:23:57 pm
content: What are you after, what is this value being used in? Does it matter if an incorrect value comes in (on initialization? faulty write by something unrelated? cosmic rays??!), and how long can it go uncorrected for? For example, a counter in a DSP loop that is "correct eventually" you might not care and even usingcounter++; if (counter == MAX_COUNT) count = 0;, i.e. it only resets after wrapping around, counting normally, one-at-a-time, again, might be acceptable. Using>=is a trivial improvement, of course, and accounts for any possible value of anunsigned. Using the mod operator with constant argument is equivalent (though it may still compile with the library implementation of that operator -- beware the cost of software long division on simpler embedded platforms).As far as undefined behavior (UB), relying on variables to overflow on their own, versus masking them explicitly, is your own choice. UB tends to perplex users unaware of it, and how the optimizer takes advantage of the instructions you've given it -- whether you're aware of, or intend to use, UB or not. A given code might give correct output from one compiler, but not be portable to another platform, or another compiler; or might give "correct" results on -O0 or -O1, but not -O3 or -Os.Note that, with some exceptions (and I don't recall exactly what they are; look up the standard), variables are "at least" sized.intis guaranteed to hold a value of +/-32767 for example. Note that -32768 might not even be guaranteed, because C includes ones-complement machines (or, it did until recently? I forget). On AVR, it might be 16 bit; ARM, i386, etc., 32 bit; maybe even 64 bit on some machines/OS/compilers.charis specifically byte sized, but I forget if that's still "at least" or guaranteed 8 bits and all that implies for overflow.Tim

author: djacobow
author_rank: Super Contributor
timestamp: December 26, 2023, 04:18:19 pm
content: Overflow onunsignedtypes isnotUB. Feel free to use it.For signed types it is indeed UB.

author: dmills
author_rank: Super Contributor
timestamp: December 26, 2023, 05:56:34 pm
content: Just a note that modulo is HORRIBLY slow in the general case (It is much worse then integer division), a conditional branch is slow, but this is orders of magnitude worse.One nice trick is to note that IFF you want to count a power of two number of possible states, you can mask instead of branching and this is really cheap.uint32_t ctr;ctr = (ctr+1) & ((1u << power_of two) -1);If power of two is say 4 (this gives 8-1 = 7 = 0000111 which masks off the high bits in ctr limiting the range to 0 - 7 without involving a branch at any point.In reality you set the mask up once at startup and locate it near to the counter for good cache locality.

author: sparkydog
author_rank: Regular Contributor
timestamp: December 26, 2023, 06:17:03 pm
content: Code:++counter &= 31u;Sorted.Or, feel free to write:Code:++counter &= ((1u << 5) - 1);Unless your compiler is an absolute piece of garbage, it should a) compute the RHS constant at compile-time and b) perform the increment and bitwise-and in a register before writing back to memory (provided you're compiling at any optimization level other than-O0, which you definitely should be doing, and provided you didn't declarecounterasvolatile).For that matter, modern compilers should even translate++counter %= (1u << N);into a bitwise-and ifcounterisunsigned. (Beware thisdoesn'twork on signed types. You'll still get better code than using an actual modulus instruction, but it needs to do some extra work to handle negatives.)

author: Mechatrommer
author_rank: Super Contributor
timestamp: December 26, 2023, 06:57:48 pm
content: Quote from: dmills on December 26, 2023, 05:56:34 pmok thats readable and reusable!how about count to 31?counter = (counter++) & 31Quote from: Veketti on December 26, 2023, 01:31:26 pmyes and no (if it does happened, someone already told you it wont)... you just lost higher bit thats all, computer will not care a bit... the implicit modulo 256... to learn more, read how registers and assembly (machine codes) work... will give you more knowledge. fwiw..

author: Veketti
author_rank: Regular Contributor
timestamp: December 26, 2023, 06:58:31 pm
content: Quote from: sparkydog on December 26, 2023, 06:17:03 pmDidn't test with cubeide, but at least online C compiler gives "error: lvalue required as left operand of assignment" from this. However if I put it to two lines:Code:++counter;counter &= 31u;it works. Should your example really work, or is the online compiler just bad?

author: dmills
author_rank: Super Contributor
timestamp: December 26, 2023, 07:03:29 pm
content: Pretty sure that counter = (counter++) & 31; is undefined behaviour, modifying counter twice between sequence points.And yea, I wondered about modifying something not an lvalue, but godbolt seems ok with it, and I am missing my copy of the standard... GCC does have a bad habit of 'extending' the language so it might be an extension or something.

author: Mechatrommer
author_rank: Super Contributor
timestamp: December 26, 2023, 07:13:21 pm
content: Quote from: dmills on December 26, 2023, 07:03:29 pmso i guess its a good habit to put nonambiguous precedence there in our code and not trying to outsmart the compiler... once i thought to put super efficient code everywhere so nobody can argue, but it turned out to bite me back with harder maintenance job later, so i wouldnt care if it doesnt impact performance such as in billions of tight loop with no delay() tolerated. and to OP sorry for my sloppiness i didnt put the "u" or type in binary format, that depends on the compiler anyway...

author: sparkydog
author_rank: Regular Contributor
timestamp: December 28, 2023, 06:40:39 pm
content: Quote from: Mechatrommer on December 26, 2023, 06:57:48 pmCongratulations!🎉 You made a counter thatdoesn't count!Please read up on the difference between pre- and post-increment before you hurt someone. 😉p.s. Other things being equal, pre-increment is usually faster than post-increment. Modern compilers will hopefully notice that you don't use the result, and change a post-increment to a pre-increment, but it's good practice in general to always write pre-increment unless you really need post-increment.@dmills, I don't think it's UB; as far as I know, the order of operations is well specified. It just isn't going to work as intended. The compiler might even optimize away the increment.Quote from: Veketti on December 26, 2023, 06:58:31 pmHuh. It works fine in C++; I'm pretty sure C++ specifies pre-increment as returning areferenceto the original variable. Looks like C turns it into an r-value. TIL... (This may have something to do with C not having reference types... 🤦)(I don't consider this "trying to outsmart the compiler". Along the lines of the above, my understanding is that it's perfectly well specified what should happen. It's just terse. Practically speaking, there is unlikely to be a functional difference vs. splitting it into two lines/statements.)

author: magic
author_rank: Super Contributor
timestamp: December 28, 2023, 07:55:18 pm
content: Yeah, it looks like undefined behavior. See the ruleshere.QuoteIOW, the side effect of "counter++" or "++counter" (writing back the incremented value) may occur either before or after writing back the result of % or &.Furthermore,Quotewhich means that the compiler can do literally anything with such incorrect code, including removing it altogether, assuming that it will never be executed and completely breaking earlier code which attempts to execute it anyway.

author: Mechatrommer
author_rank: Super Contributor
timestamp: December 28, 2023, 07:58:03 pm
content: Quote from: sparkydog on December 28, 2023, 06:40:39 pmcongratulation you paid attention.. but my compiler says you are not entirely right anyway...

author: sparkydog
author_rank: Regular Contributor
timestamp: December 29, 2023, 06:31:29 pm
content: Quote from: Mechatrommer on December 28, 2023, 07:58:03 pmAh, but that'syourcompiler. And, as magic just showed, we're playing with UB.In fact, your version is explicitly called out as UB in thedocumentation:Code:i = i++ + 1; // undefined behavior example in the documentationa = (a++) & 31; // your code(Your parentheses are superfluous, and the difference between+and&is immaterial.)Not only do I get different results (31 vs. 32) with both gcc and clang (both of which I'd argue are using the more sensible order of evaluation), clang even yells at me (for both your code and my code):Code:warning: multiple unsequenced modifications to 'a' [-Wunsequenced]Since I expect code bound for an MCU is more likely compiled with gcc or clang than with MSVC, I stand by my strong recommendation to stay away from your version. (But also, from mine too, I suppose. 😳)

author: Veketti
author_rank: Regular Contributor
timestamp: December 29, 2023, 07:33:18 pm
content: Excellent conversation. One more thing about this. I modified it to count up to 127:Code:++counter;counter &= 127u;But what if need to count more than that, say 128 or more. Then the result is always 0. Is there a way with this method?

author: magic
author_rank: Super Contributor
timestamp: December 29, 2023, 08:16:42 pm
content: Limiting with & works for numbers one less than a power of two: 3, 7, 15, ..., 127, 255, ...For anything else, simply use this.Code:if (++count > limit) count = 0;It's straightforward, it compiles to a few instructions and it's fast, even on MCUs which often lack hardware support for division (which means that % can be really slow).Just remember to have enough bits in the counter that it doesn't overflow before limit is reached.

author: brucehoult
author_rank: Super Contributor
timestamp: December 29, 2023, 09:40:45 pm
content: Quote from: magic on December 29, 2023, 08:16:42 pmYou can also do arbitrary limits like this:Code:int inc(int count, int limit) {count++;return count & -(count <= limit);}On many ISAs, including ones used in MCUs, this is branch-freeRISC-V RV32ICode:addi a0,a0,1sgt a1,a0,a1addi a1,a1,-1and a0,a0,a1ARMv1..ARMv6Code:add r0, r0, #1cmp r0, r1movgt r0, #0armv8-aCode:cmp w0, w1csinc w0, wzr, w0, gex86_64Code:lea ecx, [rdi + 1]xor eax, eaxcmp edi, esicmovl eax, ecx

author: magic
author_rank: Super Contributor
timestamp: December 29, 2023, 10:16:11 pm
content: Clever, but only the RISC-V assembly is even remotely similar to the C code you wrote...... which means that the conditional moves are compiler's trick...... which means the compiler will likely do the same given any other code.Let's seeCode:int inc_if(int count, int limit) {if (++count > limit) count = 0;return count;}int inc_cond(int count, int limit) {return ++count > limit? 0: count;}Code:0000000000000000 <inc_if>:0: 8d 47 01 lea eax,[rdi+0x1]3: 31 d2 xor edx,edx5: 39 f0 cmp eax,esi7: 0f 4f c2 cmovg eax,edxa: c3 retb: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]0000000000000010 <inc_cond>:10: 8d 47 01 lea eax,[rdi+0x1]13: 31 d2 xor edx,edx15: 39 f0 cmp eax,esi17: 0f 4f c2 cmovg eax,edx1a: c3 retCode:00000000 <inc_if>:0: 3001 adds r0, #12: 4288 cmp r0, r14: bfc8 it gt6: 2000 movgt r0, #08: 4770 bx lra: bf00 nop0000000c <inc_cond>:c: 3001 adds r0, #1e: 4288 cmp r0, r110: bfc8 it gt12: 2000 movgt r0, #014: 4770 bx lr16: bf00 nopDoesn't matter what you write