问题
According to accepted answer of this question What is the benefit of terminating if … else if constructs with an else clause?
There is a corruption case (in embedded system) that can cause a bool variable (which is 1 bit) differ to both True and False, it means the else
path in this code could be covered instead of be a dead code.
if (g_str.bool_variable == True) {
...
}
else if (g_str.bool_variable == False) {
...
}
else {
//handle error
}
I try to find out but there's still no clue for it.
Is it possible ? and How ?
Edit: For more clearly, I will give the declaration of the bool variable like:
struct {
unsigned char bool_variable : 1;
} g_str;
And also define:
#define True 1
#define False 0
回答1:
unsigned char bool_variable : 1
is not a boolean variable. It is a 1 bit integer bit-field. _Bool bool_variable
is a boolean variable.
A bit-field shall have a type that is a qualified or unqualified version of
_Bool
,signed int
,unsigned int
, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted. > C11dr §6.7.2.1
So right away unsigned char bool_variable : 1
, it is implementation-defined if it is allowed.
If such an implementation treated unsigned char
bit-fields like int
bit-fields, as unsigned char
range can fit in int
range, then troubles occur with 1-bit int
bit-fields. It is implementation defined if a 1-bit int
bit-field takes on the values of 0, 1
or 0, -1
. This leads to the //handle error
clause of this if()
block.
if (g_str.bool_variable == True) { // same as if (g_str.bool_variable == 1)
...
}
else if (g_str.bool_variable == False) { // same as if (g_str.bool_variable == 0)
...
}
else {
//handle error
}
The solution is to simplify the if()
test:
if (g_str.bool_variable) {
...
}
else {
...
}
With bit-fields, it is a corner in C where unsigned int
, signed int
are different, but int
bit-fields less than the full width of an int
may be treated as signed int
or unsigned int
. With bit-fields, it is best to be explicit and use _Bool
, signed int
, unsigned int
. Note: using unsigned
is synonymous with unsigned int
.
回答2:
This code may have a race condition. The magnitude of the problem will depend on exactly what the compiler emits when it compiles this code.
Here's what might be happening. Your code first checks bool_variable == True
, which evaluates false. Execution skips the first block and jumps to the else if
. Your code then checks bool_variable == False
, which also evaluates false so you fall into the final else
. You are doing two discrete tests on bool_variable
. Something else (such as another thread or an ISR) may be altering the value of bool_variable
during the brief window of time after the first test has run and before the second test.
You can avoid the problem completely by using if (bool == True) {} else {}
instead of re-testing for false. That version would only check the value once, eliminating the window where corruption can happen. The separate False
check doesn't really buy you anything in the first place since by definition a one-bit-wide field can only take on two possible values, so !True
must be the same as False
. Even if you were using a larger boolean type that could technically take on more than two discrete values, you should be using it as if it could only have two (such as 0=false, everything else=True).
This hints at a much larger problem, though. Even with only one variable check instead of two, you have one thread reading the variable and another altering it at practically the same time. The corruption occurring immediately before the True
check would possibly still give you erroneous results but be even harder to detect. You need some sort of locking mechanism (mutex, spinlock, etc) to ensure that only one thread is accessing that field at a time.
The only way to prove any of this for certain, though, is to step through it with a debugger or hardware probe and watch the value change between the two tests. If that's not an option, you may be able to de-couple the blocks by changing the else if
to if
and storing the value of bool_variable
before each of the two tests. Any time the two differ, then something external has corrupted your value.
回答3:
The way you've defined things, this wouldn't happen on an x86
. But it could happen with some compiler/cpu
combination.
Consider the following hypothetical assembly code for the if-else-else
construct in question.
mv SP(0), A # load 4 bytes from stack to register A
and A, 0x1 # isolate bit 1 i.e. bool_variable
cmp A, 0x1 # compare to 1 i.e. True
jmp if equal L1
cmp A, 0x0 # compare to 0 i.e. False
jmp if equal L2
<second else block>
jmp L3
L1:
<if block>
jmp L3
L2:
<first else block>
L3:
<code>
Now consider the hypothetical machine code for some of these instructions.
opcode-register-value machine-code corrupted-code
and A, 0x1 01 03 01 010301 010303
cmp A, 0x1 02 03 01 020301 020302
cmp A, 0x0 02 03 00 020300 020304
One or more of bit corruptions shown above will cause the code to execute the second else
block.
回答4:
The reason I wrote that example like it did, using "mybool", FALSE
and TRUE
, was to indicate that this is a non-standard/pre-standard boolean type.
Before C got language support for boolean types, you would invent your own boolean type like this:
typedef { FALSE, TRUE } BOOL;
or possibly:
#define FALSE 0
#define TRUE 1
typedef unsigned char BOOL;
In either situation you get a BOOL
type which is larger than 1 bit, and can therefore either be 0, 1 or something else.
Had I written the same example using stdbool bool
/_Bool
, false
and true
it wouldn't have made any sense. Because then the compiler might implement the code as a bit-field and a single bit can only have values 1 or 0.
In retrospect, a better example of the use of defensive programming might have been something like this:
typedef enum
{
APPLES,
ORANGES
} fruit_t;
fruit_t fruit;
if(fruit == APPLES)
{
// ...
}
else if(fruit == ORANGES)
{
// ...
}
else
{
// error
}
来源:https://stackoverflow.com/questions/35075563/how-can-bool-variable-be-not-equal-to-both-true-and-false