if ( !( (b - a) > 0) ) {
++cntsubs; // triggered 0 times
}
uint32_t time=0;
void main(){
if(time>1000){
// This might never work, compiler thinks it's always 0
// because there's no direct call to Timer ISR
// (Because it's an interrupt)
}
}
void Timer_ISR(){
time++;
}
Declaring as volatile completely changes how the compiler treats the variable:volatile uint32_t time=0;
void main(){
if(time>1000){
// This will work, compiler understands time can change at any time
// So it'll always check it, not optimizing or caching
}
}
void Timer_ISR(){
time++;
}
Use parenthesis to perform an operation before anything else.
Are a and b modified by some interrupt?
Then you must declare them as volatile, or the compile won't expect them randomly changing, ex.:
Hi all,
In an application I use some timestamps (signed 32 bit integers counting μs since system reset) and I want to test if they have elapsed comparing them against current time. Now consider this code (compiled with armclang for Cortex-M4):Code: [Select]int a, b, cntcomp, cntsubs;
a = 0;
cntcomp = 0;
cntsubs = 0;
do {
b = a + 5; // b is "always" greater that n
if (!(b > a)) {
++cntcomp; // triggered 5 times, when previous add overflows
}
if (!(b - a > 0)) {
++cntsubs; // triggered 0 times
}
} while (++a);
int timeHasElapsed(unsigned timeStamp, unsigned now){
return (int)(now - timeStamp) > 0;
}
The correct way to do this is to store the timestamps as unsigned -- which the compiler must treat as modulo(2^32) arithmetic -- and to cast the result of the subtraction to signed.
The correct way to do this is to store the timestamps as unsigned -- which the compiler must treat as modulo(2^32) arithmetic -- and to cast the result of the subtraction to signed.
Strictly speaking, this is not entirely flawless either. While subtraction of two unsigned integers indeed won't overflow thanks to second sentence of C99 6.2.5#9, casting unsigned to signed of the same width is implementation defined (6.3.1.3#3), so you have to check documentation of your compiler and accept that it's non-portable.
int timeHasElapsed(unsigned timeStamp, unsigned now){
return ((now - timeStamp - 1) & 0x80000000) == 0;
}
Hi all,
In an application I use some timestamps (signed 32 bit integers counting μs since system reset) and I want to test if they have elapsed comparing them against current time. Now consider this code (compiled with armclang for Cortex-M4):Code: [Select]int a, b, cntcomp, cntsubs;
a = 0;
cntcomp = 0;
cntsubs = 0;
do {
b = a + 5; // b is "always" greater that n
if (!(b > a)) {
++cntcomp; // triggered 5 times, when previous add overflows
}
if (!(b - a > 0)) {
++cntsubs; // triggered 0 times
}
} while (++a);
What are you actually trying to do here?
The test code is crazy because the compiler can easily see the fixed relationship between a and b. And since you declared them as signed types, the compiler is entitled to assume that you know what you are doing and a will never have a value that makes a+5 overflow. It can assume the mathematical relationship between a and b.
A correct thing for the compiler to do here is to replace the whole thing with "a=cntcomp=cntsubs=0". And in fact I see gcc -O1 doing exactly that.
The correct way to do this is to store the timestamps as unsigned -- which the compiler must treat as modulo(2^32) arithmetic -- and to cast the result of the subtraction to signed.
My problem is that I cannot (easily) coerce the compiler to always perform the comparison in the right way, and the same (b - a > 0) expression above, placed in other part of the program (or using different compiler options), may result in the wrong test.
If so, this is a bug in the compiler.
Subtraction is the same whether arguments are signed or unsigned
Nope - unsigned subtraction has standard defined behavior in case of overflow. Signed does not. And, timestamps are the most usual example where overflows happen by design.
Of course, if the result never overflows, then it does not matter, but the OP said they are counting microseconds from boot. So if the thing runs longer than 35 minutes, an overflow will happen. Therefore, unsigned type should be used. Signed type can be used if the code is designed to work with a specific compiler (and version), and the documentation of the compiler or disassembly/testing of the code proves the code works correctly. But this makes no sense, as you can write standard portable code just as easily, by just using unsigned type.
Do you have any real-world examples where a compiler generated any code which produced incorrect overflow for signed integers?
I hunted for such a bug for months. It was a wheel position count, in int16_t, and difference between the previous and current count (every millisecond or so), correct result being almost always -1, 0 or +1 (maybe sometimes -2 or +2, when going really fast). When the count was wrapping around +/-32768, the result was a large number, so a sudden jump in integration occurred.
(...)
Relying on UB is rarely a good idea, even if you think you are smart enough to do it.
Not rarely: NEVER.
In an application I use some timestamps (signed 32 bit integers counting μs since system reset)
If a and b are signed, the result will be signed too, so the comparison will work. If they're unsigned, the result will be unsigned and the comparison will not work, so you would have to cast or isolate the sign bit.
uint8_t calc() {
uint8_t a = 0;
uint8_t b = 1;
return (a - b) >> 1;
}
uint8_t calc() {
uint8_t a = 0;
uint8_t b = 1;
return (a - b) >> 32;
}
Only here the compiler will warn that '32' is wider than the type width. That warning threshold is passed at 31/32 shifts, meaning that (a-b) returns an int32, which is arithmetically shifted right (sign extension), and then downcasted to a uint8_t for the out result.What processor doesn't have Carry/Borrow bit? That's one of the most basics CPU features since... ever?
I remember a discussion we had on RISC-V regarding carry flags and how they were a pain to deal with, in particular for instruction dependency in a pipeline.
One further example: there are no conditional branch instructions but instead conditional skips, so to conditionally branch you skip one instruction and goto !
blt r12,r13,.+345 // ±4 KB range
movf 13,1
subwf 12,w
btfsc 3,0 // C is bit 0 of register 3
goto 345 // relative addressing not supported
cmp r12,r13
blt .+345
slt r8,r12,r13
bne r8,.+345
cmp/ge r13,r12 // result stored in T bit
bt .+345 // branch if T bit is True
The video I linked said that unsigned-unsigned should return unsigned, but still, I can't find a specific mention of that specification in the manual.
Edit: I seem to be somewhat mistaken, it seems like "integer promotion" does not happen for types that are already unsigned 32-bit..
he purpose is to determine a common real type for the operands and result.
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed.