1

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
Community
  • 1
  • 1
Van Tr
  • 5,889
  • 2
  • 20
  • 44
  • 1
    You need to show the declaration of `bool_variable`. – Jonathon Reinhart Jan 29 '16 at 01:56
  • 3
    maybe the type of `bool_variable` is not actually `_Bool`. BTW whatever it is, it will be at least 8-bit. There are no 1-bit variables other than bitfields . – M.M Jan 29 '16 at 01:58
  • 1
    To get path coverage, typically use `if (bool_variable)` and `else`. – M.M Jan 29 '16 at 01:58
  • 1
    Paraphrasing from the other question referenced: Memory corruption or overflows could cause garbage values to be overwritten there. In general, unless you have a need to be super defensive in your code (writing an OS or something to protect nuclear launch codes) you shouldn't worry about it. Use the pattern `if (boolvar) {...} else {...}` instead of comparing to true/false – Krease Jan 29 '16 at 02:00
  • please see my question after edited – Van Tr Jan 29 '16 at 02:05
  • It can be neither true nor false if whoever is paying you to write the code says so. If they [require you](http://stackoverflow.com/questions/35053371/why-should-if-else-if-constructs-be-terminated-with-an-else-clause) to use the `else` clause, just do it. – e0k Jan 29 '16 at 02:09
  • 2
    In Standard C, bitfields may only be `_Bool`, `int` or `unsigned int`. It is implementation-defined whether `unsigned char bool_variable : 1;` is allowed. This means your compiler should document the behaviour of this code and explain what the permitted values are. – M.M Jan 29 '16 at 02:11
  • If the third branch is entered, a possibility would be that your program already caused undefined behaviour before that point. – M.M Jan 29 '16 at 02:12
  • generally in C boolean logic on a variable is either zero or not zero, so for a 32 bit integer for example there is only one way to get false, but (2^32)-1 values that are true. And in your case it looks like you have one way for true, one way for false and (2^N)-2 ways for else whatever N is. – old_timer Jan 29 '16 at 02:12
  • 1
    As a matter of style it makes little sense to compare boolean values for equality to `True` or `False`. Rather than `if (b == true)`, write `if (b)`. Rather than `if (b == false)`, write `if (!b)`. – Keith Thompson Jan 29 '16 at 02:50
  • @dwelch: But since the object in question is a 1-bit bit field, N is 1 and there should be no possible values other than 0 and 1. – Keith Thompson Jan 29 '16 at 02:56
  • @M.M: True -- but since `unsigned char` is an unsigned type, a compiler that did anything other than permitting only values `0` and `1` for an `unsigned char :1` bit field would be, if not non-conforming, at least batcrackers insane. – Keith Thompson Jan 29 '16 at 02:57
  • then why bother comparing with the variables True or False? if (variable) else is all you need for a single bit, if it is truly a single bit. otherwise it is sampling more than once and the value is changing between samples. – old_timer Jan 29 '16 at 03:12
  • Have you initialized `g_str` before testing it? Does the code consistently execute the `else` clause, or only sometimes? You mention "corruption"; do you have evidence, other than the `else` clause being executed, that something is being corrupted? Since it might be relevant, what embedded system are you targeting, and what compiler are you using? – Keith Thompson Jan 29 '16 at 03:27
  • Have you actually encountered this, or is it a theoretical question? – Keith Thompson Jan 29 '16 at 03:39
  • @KeithThompson yes, I encountered this cases sometimes. I do the testing for C embedded code and sometimes I see this case and I always need to consider it as code coverage exception. – Van Tr Jan 29 '16 at 04:07
  • I think I should close this question. It's too broad or it's simple cannot happened because it's just a rule. – Van Tr Jan 29 '16 at 04:10
  • You can *delete* the question if you like. But if you want to leave it open, please answer the questions in my previous comment. – Keith Thompson Jan 29 '16 at 05:34

4 Answers4

2

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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    A conforming implementation *could* permit the values `0` and `-1` for a 1-bit `unsigned char` bit field, but it would be nuts to do so for an explicitly unsigned type. I wouldn't believe the OP is having that particular problem without proof. And there are valid reasons to use implementation-defined bit-field types rather than the standard-guaranteed types. Though the standard doesn't suggest this, the declared type of a bit can affect the size of the containing structure; I think some ABIs require this. – Keith Thompson Jan 29 '16 at 03:21
  • 1
    @Keith Thompson Agree about being nuts and agree there are reasons for using bit-fields. Memory mapped hardware registers come to mind. Yet with all the potential for implementation defined behavior, and highly optimized compilers, using bit masks, shifts and fixed-width unsigned types is less trouble for portable applications than bit-fields. – chux - Reinstate Monica Jan 29 '16 at 03:25
  • Or using a bit-field of type `_Bool` (if the compiler is modern enough to support it; a compiler for an embedded system might not be). – Keith Thompson Jan 29 '16 at 03:29
1

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.

bta
  • 43,959
  • 6
  • 69
  • 99
1

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.

Ziffusion
  • 8,779
  • 4
  • 29
  • 57
1

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
}
Lundin
  • 195,001
  • 40
  • 254
  • 396