EEVblog Electronics Community Forum
Electronics => Microcontrollers => Topic started by: Simon on October 09, 2018, 08:24:57 am
-
#define BIT_GET(p,m) ((p) & (BIT(m)))
#define BIT(x) (0x01 << (x))
So I get the bit masking stuff but but how is some random byte that is the result is understood as a boolean state.
So 01010101 & 00000100 = 00000100
How does 00000100 equate the Boolean state of 1 ?
-
All numbers are interpreted as boolean 1, except zero which is boolean 0
eg, these will loop forever because 4 is evaluated as true (1)
while (4)
{
}
while (0b00000100)
{
}
-
Right, perfect, thank you.
-
Yes - what you did is thus perfectly legit.
There is no boolean type per se in C. Any integer different from 0 is considered true in any conditional statement, and any integer equal to 0 is false.
I often write conditions such as "xxx & MASK". They are compact and readable.
A corollary (and pitfall for people used to C++ only for instance) of this is that you should never assume any C expression used as a condition to be exactly 1 when true, or any other magic value. It can be any value other than zero.
One common way of "normalizing" a boolean expression in C is to use a double logic negation: if you write "!!(xxx & MASK)" you should get exactly either 0 or 1. I've seen this in various source codes. Note that it's absolutely not required, as manipulating the result value of a condition makes no sense to me. Some people may be tempted to use this in an attempt to optimize expressions (for instance multiplying a "boolean" value within some expression so as to avoid using an explicit test), but I consider this obfuscating, and decent modern compilers will make optimizations that are much better than those hand-written tricks anyway.
-
There is no boolean type per se in C.
Since 1999 you can include stdbool.h to enable the bool type. This type can only have the value of 0 or 1.
-
Yes, but this is essentially sugar coating. Conditional expressions can still be plain integers. And I've just re-checked the standard: there's nothing there that I've seen that actually guarantees by standard that a 'bool' variable will hold only 0 or 1 either. It may or may not depending on the compiler's implementation as far as I've understood it. The header just declares a macro 'bool' that expands to '_Bool'.
The standard only states that "The type_Bool and the unsigned integer types that correspond to the standard signed integer types are the standard unsigned integer types." and that "An object declared as type _Bool is large enough to store the values 0 and 1." Large enough doesn't mean "only". Nothing prevents the compiler from just using an unsigned integer as _Bool (as is suggested in the other sentence). Just states that it should be at least 1 bit wide ;)
Having tested it on GCC, GCC actually seems to reduce values to 0 or 1 on bool assignments, but I don't think it's required. I'm taking this from the C 2011 std though, so that may have evolved a bit.
I've actually been using this type for years now, but essentially to convey meaning and improve readability. It doesn't change squat to the behavior as far as I'm concerned. And again, expecting a specific integer value for a bool would be neither wise nor readable IMHO.
-
i don't bother with bool's as it's an 8 bit CPU anyway so nothing gained.
-
i don't bother with bool's as it's an 8 bit CPU anyway so nothing gained.
I'd still suggest using the bool type for boolean value storage as this has become part of the standard and will be easier to read than using the pervasive "int" instead that we used to use. But other than that, it doesn't change much. I find it more readable though. But again, I strongly advise against assuming that it will get any particular value.
In the same vein, I strongly suggest using the stdint.h header and the standard integer types in it, especially in embedded software. They have added a much-needed standard way of declaring variables with exact bit width and signedness (such as 'uint8_t', etc), whereas before that we used all kinds of tricks for that that were largely non-portable.
Concerning the macros that you submitted, that doesn't change anything at all. They are still valid and no mention of the bool type is necessary. So the bool type is mainly for variable/struct members declaration.
-
And I've just re-checked the standard: there's nothing there that I've seen that actually guarantees by standard that a 'bool' variable will hold only 0 or 1 either.
6.3.1.2 would seem pretty clear to me. :-//
6.3.1.2 Boolean type
When any scalar value is converted to _Bool, the result is 0 if the value compares equal to 0; otherwise, the result is 1.
But of course:
bool b;
*(uint8_t *)&b = 123;
I would say that's UB, though (I have to check).
EtA: I have checked, 6.5 clause 7, first bullet.
-
I'd still suggest using the bool type for boolean value storage as this has become part of the standard and will be easier to read than using the pervasive "int" instead that we used to use.
... or at least don't use "int" on 8-bitters unless you are sure about what you are doing - it's likely to be reproduced as 16-bit integer, as the C standard requires IIRC, with the obvious overhead of software emulation of 16-bit arithmetic, and memory consumption. If it's a volatile used to pass information between the ISR and the main loop, the compiler doesn't even have a chance to optimize it. And then if you make the mistake to expect it's atomic, like the 8-bit bool would be... |O
In the same vein, I strongly suggest using the stdint.h header and the standard integer types in it, especially in embedded software.
Yes, 110% this. Even if you don't end up using bool, you need standard readable types of integers. The options are, define your own (like we did before the standard existed) in your own header, or use the standard way of stdint or inttypes.
-
i don't bother with bool's as it's an 8 bit CPU anyway so nothing gained.
you gain in readability. I am gradually updating all the old firmwares to remove every bit of assembly that don't compromize functionality, every implicit bitfield converted to a bitfield structure and so on.
-
Actually some compilers (like the Microsoft dinosaur I use for work) will give you a ‘performance warning’ if you assign an int to a bool. I think the compiler uses some tricks to make manipulating bool types faster.
So I also agree - use the bool type for booleans and not just because it make the code make sense.
Tom
Sent from my iPhone using Tapatalk
-
6.3.1.2 Boolean type
When any scalar value is converted to _Bool, the result is 0 if the value compares equal to 0; otherwise, the result is 1.
True. The key is that it needs a conversion to do this. The underlying storage itself can be as wide as the compiler wants, making it thus possible that a "bool" declared variable would hold something else than 1 or 0 in some cases.
My point is that IMO, it's bad practice to assume only 0 or 1 for a conditional expression in C in general, and integer expressions that are not of type 'bool' are still valid in C as conditions. Thus I happen to actually not care and not *want* to care. I still think this is some kind of sugar coating as C doesn't strictly *require* a boolean-type expression for conditions, so it's only half-integrated in the C language IMO (obviously not to break existing code, so it makes perfect sense). Now if it was required as in very strongly-typed languages such as ADA, that would be another story.
My biggest pet peave with "boolean" expressions in C is as I mentioned the "trick" expressions that could be encouraged by this and that are hard to read, would break in non-compliant compilers and should be avoided at all costs IMO (optimizers being much more efficient for this anyway):
bool b = W & MASK;
int n = b*10 + 1;
-
My point is that IMO, it's bad practice to assume only 0 or 1 for a conditional expression in C in general, and integer expressions that are not of type 'bool' are still valid in C as conditions. Thus I happen to actually not care and not *want* to care. I still think this is some kind of sugar coating as C doesn't strictly *require* a boolean-type expression for conditions, so it's only half-integrated in the C language IMO
stdbool.h in XC8 V2.00 has
#define false 0
#define true !false
:)
i havent' checked in other compilers but i don't expect to see anything different
-
Small example:
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
int main(void)
{
bool b1;
union { bool b; int n; } b2;
b1 = 25;
printf("b1 = %d\n", b1);
printf("sizeof b1 = %"PRIu64"\n", sizeof b1);
b2.n = 10;
printf("b2.b = %d\n", b2.b);
}
On an x64 target with GCC gives:
b1 = 1
sizeof b1 = 1
b2.b = 10
Implicit conversions are only done while assigning bool variables.
-
stdbool.h in XC8 V2.00 has
#define false 0
#define true !false
Not sure you got the main point. First because those are just constants that are guaranteed to be seen as either false or true in logic/conditional expressions, nothing more. Second because those macros are valid even for very old versions of C. This is exactly how TRUE and FALSE constants have been declared in lots of various C headers for decades. Obviously those declarations are correct: they are just tautologies. Not false is true, not true is false. And 0 is false in C. What else does it actually say? :D
Again there is no bijection in C between the "true" logic state and the value "1". There only is for the "0" value.
There kind of is a bijection if you strictly stay in the realm of 'bool'-typed expressions with C >= 99, with pitfalls. And again, I personally don't care and think this is something that should have been avoided in the C standard, for reasons given above and mostly because it leaves an inconsistency (again since any value other than 0 in C is still considered true, even pointers). A true boolean type integrated in C should have prevented IMO any implicit conversion from any other type (and only allow expressions with bool values and logic operators). Just my opinion of course. Again they have obviously chosen a compromise not to break existing code, thus the half-baked solution.
-
Actually some compilers (like the Microsoft dinosaur I use for work) will give you a ‘performance warning’ if you assign an int to a bool. I think the compiler uses some tricks to make manipulating bool types faster.
If the compiler in question supports the bool type and C99, it's not completely a dinosaur. Microsoft's older compilers are known not to support C99 very well though, or only partially.
In your example, the warning makes perfect sense and I think you considered it backwards. As newbrain pointed out and I discussed afterwards (and showed an example), assigning an integer to a bool will do an implicit conversion and thus will "clip" the integer value to one if it's different from zero. This clipping operation costs more than a simple assignment. Thus the performance warning.
Due to this, depending on the compiler and the assignments/expressions that are used, using the bool type may thus actually be less efficient than keeping your logic expressions as integers.
Again, it mostly adds to readability, prevents (or should) developers from declaring their own boolean types all over the place, and may have been a decision to sort of bridge a gap between C and C++ concerning boolean values, but not much more. Any decent optimizer will optimize your logic expressions probably just as well if you don't use the bool type. As seen above, most compilers (GCC does at least) will store 'bool' variables as 8-bit integers (although it's not strictly required, but makes sense) though, and thus they will take less storage space than an 'int' on most targets, so that can be a plus.
-
b2.n = 10;
printf("b2.b = %d\n", b2.b);
.
Storing something in a union member and retrieving it from a different member invokes undefined behavior. Anything can happen, so I don't know what this example was supposed to prove.
-
Storing something in a union member and retrieving it from a different member invokes undefined behavior. Anything can happen, so I don't know what this example was supposed to prove.
Yeah, per standard. And standard is just paper.
Yes, demons may fly out of your nose. In theory.
In reality, unions are used exactly for this purpose so widely, that every attempt to make a compiler break the existing software gets immediate backlash. This has been the case for decades - especially in embedded.
Union is simply really useful for this purpose.
More discussion here: https://stackoverflow.com/questions/252552/why-do-we-need-c-unions - the best accepted answer agrees with my viewpoint.
But I guess this is a so-called "opinion" thing and I respect anyone's idea of adhering to the standard to the letter.
-
What happens down the line with this bool with a value of 10? The compiler is free to optimize the use of the bool and might generate code that only looks at bit 0.
-
Yes in practice this has been so widely used that no compiler that I've ever run into would try and map union fields on different offsets. So this is basically equivalent to using pointers. If you're going to get stuck up about it, you may even say that I made assumptions as to the endianness. The exact result doesn't matter anyway.
Although it didn't really even need to be proven, it just shows that a bool declared variable may contain many other values than 0 and 1 and that again, the bool type in C is not much more than sugar coating. For the rest of my point read above.
-
What happens down the line with this bool with a value of 10? The compiler is free to optimize the use of the bool and might generate code that only looks at bit 0.
For constant values, the "optimization" will occur at compile time anyway, not at run time.
The compiler is free to do whatever it wants if you only use bool's in conditions (same as with plain int expressions). If you use bool's values in arithmetic expression such as the example I gave, it's not. It will coerce it into 0 or 1. Again I find this madness to do so anyway.
As for the above example of 10, I'm afraid looking at bit 0 won't tell you much as 10 is "true" in the C logic sense yet its bit 0 is 0. But that's just a detail. Point is, yes optimizers will still do whatever they want/can to simplify expressions in ways that may be far away from what you would by hand.
-
Although it didn't really even need to be proven, it just shows that a bool declared variable may contain many other values than 0 and 1 and that again, the bool type in C is not much more than sugar coating.
I didn't argue that something odd would happen inside the union. I fully expect the bool to "have" the value 10 in the example. What I am arguing is that the compiler is free to assume the bool will never have a value other than 0 and 1. Going behind the back of the compiler to set bits ad hoc can lead to problems, both for bool, pointers and other data types.
-
stdbool.h in XC8 V2.00 has
#define false 0
#define true !false
Not sure you got the main point. First because those are just constants that are guaranteed to be seen as either false or true in logic/conditional expressions, nothing more. Second because those macros are valid even for very old versions of C. This is exactly how TRUE and FALSE constants have been declared in lots of various C headers for decades. Obviously those declarations are correct: they are just tautologies. Not false is true, not true is false. And 0 is false in C. What else does it actually say? :D
Again there is no bijection in C between the "true" logic state and the value "1". There only is for the "0" value.
There kind of is a bijection if you strictly stay in the realm of 'bool'-typed expressions with C >= 99, with pitfalls. And again, I personally don't care and think this is something that should have been avoided in the C standard, for reasons given above and mostly because it leaves an inconsistency (again since any value other than 0 in C is still considered true, even pointers). A true boolean type integrated in C should have prevented IMO any implicit conversion from any other type (and only allow expressions with bool values and logic operators). Just my opinion of course. Again they have obviously chosen a compromise not to break existing code, thus the half-baked solution.
of course i got it, i was just :horse: :)
-
i don't bother with bool's as it's an 8 bit CPU anyway so nothing gained.
I don't see why it couldn't gain something on some platforms where the compiler could use bits to store bools and bitwise instructions to manipulate them
-
ARM missed the chance to add true single-bit variables as a extension to their compilers, in conjunction with the "bit-banding" hardware feature.
It could've worked really nicely - pointers to single bits would have worked fine, and everything.But it would have been non-standards-conforming, and seemed to mostly be aimed at IO ports that were better handled via other mechanisms, so they left it for users to do on their own, and so it mostly "didn't happen." And now most new ARMs don't even implement bit-banding, so the the opportunity is gone...
-
ARM missed the chance to add true single-bit variables as a extension to their compilers, in conjunction with the "bit-banding" hardware feature.
It could've worked really nicely - pointers to single bits would have worked fine, and everything.But it would have been non-standards-conforming, and seemed to mostly be aimed at IO ports that were better handled via other mechanisms, so they left it for users to do on their own, and so it mostly "didn't happen." And now most new ARMs don't even implement bit-banding, so the the opportunity is gone...
afaiu bit banding was dropped because it doesn't play well with data caches
-
Actually some compilers (like the Microsoft dinosaur I use for work) will give you a ‘performance warning’ if you assign an int to a bool. I think the compiler uses some tricks to make manipulating bool types faster.
If the compiler in question supports the bool type and C99, it's not completely a dinosaur. Microsoft's older compilers are known not to support C99 very well though, or only partially.
In your example, the warning makes perfect sense and I think you considered it backwards. As newbrain pointed out and I discussed afterwards (and showed an example), assigning an integer to a bool will do an implicit conversion and thus will "clip" the integer value to one if it's different from zero. This clipping operation costs more than a simple assignment. Thus the performance warning.
No! Very much not what I meant. I think that using bool adds readability and might even provide a speed-up as the compiler can make assumptions about bool types (i.e. they are effectively just a single bit).
I'd go further in fact and advocate explicit conversions so for example if you have an expression that is non-boolean, (like the result of a bit mask operation) then I think it is good to explicitly convert it to a boolean value by comparing it with zero. This means you are doing the conversion where you choose and you reduce the chance of weird operator precedence issues like it multiplying a number by a the result of a boolean expression.
Tom
-
#define BIT_GET(p,m) ((p) & (BIT(m)))
#define BIT(x) (0x01 << (x))
So I get the bit masking stuff but but how is some random byte that is the result is understood as a boolean state.
So 01010101 & 00000100 = 00000100
How does 00000100 equate the Boolean state of 1 ?
If you want to get a result of only 00000000 or 00000001 then use this instead:
#define BIT_GET(p,m) (((p)>>(m)) & 1)
That's a bit more work for the CPU as the shift is done at run time instead of compile time. If you've got a proper multi-bit shifter then it's likely to be only one extra instruction/cycle and so not to be worried about.
-
#define BIT_GET(p,m) (((p)>>(m)) & 1)
That's a bit more work for the CPU as the shift is done at run time instead of compile time.
If m is a constant, the compiler will optimize weirdly for you...bool get_b6() {
return BIT_GET(PORTB, 6);
0: 85 b1 in r24, 0x05 ;; read portb
2: 86 fb bst r24, 6 ;; set T status bit from bit 6
4: 88 27 eor r24, r24 ;; zero return value
6: 80 f9 bld r24, 0 ;; load bit 0 (true or false) from T
8: 08 95 ret
I get versions using a shift & and (b1), just an and (b0), and the carry bit (b7)
If you've got a proper multi-bit shifter then it's likely to be only one extra instruction/cycle and so not to be worried about.
Alas, no "proper" shifter on any of the AVR chips. going to a variable bit number gets you a loop. Computed with a 16bit int, too. Sad.
bool get_bv(uint8_t b) {
return BIT_GET(PORTB,b);
2c: 25 b1 in r18, 0x05 ; 5
2e: 30 e0 ldi r19, 0x00 ; 0
30: a9 01 movw r20, r18
32: 00 c0 rjmp 38
34: 55 95 asr r21
36: 47 95 ror r20
38: 8a 95 dec r24
3a: 02 f4 brpl 34
3c: ca 01 movw r24, r20
3e: 81 70 andi r24, 0x01 ; 1
40: 08 95 ret
-
Why not
#define BIT_GET(p,m) ( (p) & (BIT(m)) != 0 )
#define BIT(x) (0x01 << (x))
-
Needs another set of parens, after which it produces exactly the same code (for AVR) as the previous macro.
-
Ok yes brackets...
Reasonably surprised that the generated code is the same as the previous definition - #define BIT_GET(p,m) (((p)>>(m)) & 1).
The BIT() macro should evaluate to a compiler constant.
Then BIT_GET(p,m) assuming M is a constant evaluates to ((p & constant) != 0)
I only suggest adding the !=0 as then the result is a boolean and not a number.
So I assume it is used like
if ( BIT_GET(something,BIT(3)) )
{
...
-
yep. As you say. The macros evaluate to the same code on AVR, CM0, and CM4. (all gcc, so that's not really surprising.)CM4 ends up using the "bit field extract" instruction, which is pretty nice.
here's code that should be pretty platform-independent:#include <stdint.h>
volatile uint8_t PORTB;
#define BIT_GET(p,m) ( ((p) & (BIT(m))) != 0 )
#define BIT(x) (0x01 << (x))
#define BIT_GET1(p,m) ((((uint8_t)p)>>((uint8_t)m)) & (uint8_t)1)
bool get_b3() {
return BIT_GET(PORTB, 3);
}
bool get1_b3() {
return BIT_GET1(PORTB, 3);
}
bool get_bv(uint8_t b) {
return BIT_GET(PORTB,b);
}
bool get1_bv(uint8_t b) {
return BIT_GET1(PORTB,b);
}
int main() {}
extern "C" { void _exit(int c) {while(1);} }
-
#include <stdint.h>
#include <stdbool.h>
static inline bool bit_get(volatile uint8_t* p, uint8_t b){
return *p & (1<<b) ? 1 : 0;
}
static inline void bit_set(volatile uint8_t* p, uint8_t b, bool tf){
if(tf) *p |= (1<<b); else *p &= ~(1<<b);
}
void main()
{
//just guessing that 0x24 will get me porta, 0x25 portb (IN/OUT instructions)
//PORTA.3 = PORTB.6
bit_set((volatile uint8_t*)0x24, 3, bit_get((volatile uint8_t*)0x25,6));
for(;;);
}
/*
Disassembly of section .text:
00000000 <main>:
0: 2e 9b sbis 0x05, 6 ; 5
2: 02 c0 rjmp .+4 ; 0x8 <__zero_reg__+0x7>
4: 23 9a sbi 0x04, 3 ; 4
6: 01 c0 rjmp .+2 ; 0xa <__zero_reg__+0x9>
8: 23 98 cbi 0x04, 3 ; 4
a: ff cf rjmp .-2 ; 0xa <__zero_reg__+0x9>
*/
-
Yeah; except your use of inlines, and compiler optimization, means that you never actually have a "bool."
I figure I'm a bit upset that I don't get: ldi r24, 1 ;; assume true sbis PORTB,6 ;; test IO bit
xor r24, r24 ;; we were wrong; set false. ret
-
bool sw_is_on()
{
return bit_get((volatile uint8_t*)0x25, 6);
}
/*
00000000 <sw_is_on>:
0: 85 b1 in r24, 0x05 ; 5
2: 86 fb bst r24, 6
4: 88 27 eor r24, r24
6: 80 f9 bld r24, 0
8: 08 95 ret
exact same result as the macro
-
#define BIT_GET(p,m) ((p) & (BIT(m)))
#define BIT(x) (0x01 << (x))
So I get the bit masking stuff but but how is some random byte that is the result is understood as a boolean state.
So 01010101 & 00000100 = 00000100
How does 00000100 equate the Boolean state of 1 ?
Its bit wise. So each bit is operated on by its position.
Sent from my SM-G930V using Tapatalk