【问题标题】:How can bool variable be not equal to both True and False?bool 变量如何不等于 True 和 False?
【发布时间】:2016-01-29 01:53:41
【问题描述】:

根据这个问题的接受答案 What is the benefit of terminating if … else if constructs with an else clause?

有一个损坏情况(在嵌入式系统中)可能导致 bool 变量 (1 位) True 和 False 不同 strong>,这意味着这段代码中的else路径可以被覆盖而不是死代码。

if (g_str.bool_variable == True) {
 ...
}
else if (g_str.bool_variable == False) {
 ...
}
else {
 //handle error
}

我试图找出答案,但仍然没有任何线索。

有可能吗?如何?

编辑:为了更清楚,我将给出 bool 变量的声明,如:

struct {
   unsigned char bool_variable : 1;

} g_str;

同时定义:

#define True 1
#define False 0

【问题讨论】:

  • 你需要出示bool_variable的声明。
  • 可能bool_variable 的类型实际上并不是_Bool。顺便说一句,不管它是什么,它至少是 8 位的。除了位域,没有 1 位变量。
  • 要获得路径覆盖,通常使用if (bool_variable)else
  • 从引用的另一个问题转述:内存损坏或溢出可能导致垃圾值在那里被覆盖。一般来说,除非您需要在代码中进行超级防御(编写操作系统或其他东西来保护核发射代码),否则您不应该担心它。使用模式if (boolvar) {...} else {...} 而不是比较真/假
  • 在标准 C 中,位域只能是 _Boolintunsigned int。是否允许unsigned char bool_variable : 1; 是实现定义的。这意味着您的编译器应该记录此代码的行为并解释允许的值是什么。

标签: c embedded


【解决方案1】:

unsigned char bool_variable : 1 不是布尔变量。它是一个 1 位整数位域。 _Bool bool_variable 是一个布尔变量。

位域的类型应为_Boolsigned intunsigned int 或其他一些实现定义的类型的合格或非合格版本。是否允许原子类型是实现定义的。 > C11dr §6.7.2.1

所以马上unsigned char bool_variable : 1,如果允许的话就是实现定义的。


如果这样的实现将unsigned char 位域处理为int 位域,因为unsigned char 范围可以适合int 范围,那么1 位int 位域会出现问题。如果 1 位 int 位字段采用 0, 10, -1 的值,则它是实现定义的。这导致了这个 if() 块的 //handle error 子句。

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
}

解决办法是简化if()测试:

if (g_str.bool_variable) {
 ...
}
else  {
 ...
}

对于位域,它是 C 中unsigned intsigned int 不同的角落,但小于int 的全宽的int 位域可以被视为signed intunsigned int。对于位域,最好是明确的并使用_Boolsigned intunsigned int。注意:使用unsignedunsigned int 的同义词。

【讨论】:

  • 符合要求的实现可以允许 1 位 unsigned char 位字段的值 0-1,但对于一个显式无符号类型。如果没有证据,我不会相信 OP 会遇到这个特殊问题。并且有充分的理由使用实现定义的位字段类型而不是标准保证类型。虽然标准没有建议这一点,但声明的位类型会影响包含结构的大小;我认为一些 ABI 需要这个。
  • @Keith Thompson 同意疯狂并同意使用位域是有原因的。内存映射硬件寄存器浮现在脑海中。然而,由于实现定义的行为和高度优化的编译器的所有潜力,使用位掩码、移位和固定宽度的无符号类型对于可移植应用程序来说比位字段的麻烦要少。
  • 或使用_Bool 类型的位域(如果编译器足够现代以支持它;嵌入式系统的编译器可能不支持)。
【解决方案2】:

此代码可能存在竞争条件。问题的严重程度将取决于编译器在编译此代码时发出的确切信息。

这就是可能发生的事情。您的代码首先检查bool_variable == True,其评估结果为假。执行跳过第一个块并跳转到else if。然后,您的代码检查bool_variable == False,它也评估为假,因此您落入最终的else。您正在对bool_variable 进行两个离散测试。在第一个测试运行之后和第二个测试运行之前的短暂时间窗口内,其他东西(例如另一个线程或 ISR)可能会改变 bool_variable 的值测试。

您可以通过使用if (bool == True) {} else {} 来完全避免该问题,而不是重新测试是否为假。该版本只会检查一次值,从而消除可能发生损坏的窗口。单独的False 检查并没有真正为您带来任何好处,因为根据定义,一位宽的字段只能采用两个可能的值,因此!True 必须与False 相同。即使您使用的是较大的布尔类型,从技术上讲可以采用两个以上的离散值,您也应该像只能有两个一样使用它(例如 0=false,其他所有值=True)。

不过,这暗示了一个更大的问题。即使只有一个变量检查​​而不是两个,您也有一个线程读取变量,另一个线程几乎同时更改它。在True 检查之前发生的损坏可能仍然会给您错误的结果,但更难检测。您需要某种锁定机制(互斥锁、自旋锁等)来确保一次只有一个线程访问该字段。

不过,唯一可以肯定地证明这一点的方法是使用调试器或硬件探针逐步完成它,并观察两个测试之间的值变化。如果这不是一个选项,您可以通过将else if 更改为if 并在两个测试中的每一个测试之前存储bool_variable 的值来解耦块。每当两者不同时,就会有外部因素破坏了您的价值。

【讨论】:

    【解决方案3】:

    按照您定义事物的方式,x86 不会发生这种情况。但它可能发生在一些 compiler/cpu 组合中。

    考虑以下关于if-else-else 构造的假设汇编代码。

        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>
    

    现在考虑其中一些指令的假设机器代码。

                 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
    

    上面显示的一个或多个位损坏将导致代码执行第二个else 块。

    【讨论】:

      【解决方案4】:

      我之所以像这样使用“mybool”、FALSETRUE 编写该示例,是为了表明这是一个非标准/准标准的布尔类型。

      在 C 语言支持布尔类型之前,您可以像这样发明自己的布尔类型:

      typedef { FALSE, TRUE } BOOL;
      

      或者可能:

      #define FALSE 0
      #define TRUE  1
      typedef unsigned char BOOL;
      

      在任何一种情况下,您都会得到一个大于 1 位的 BOOL 类型,因此可以是 0、1 或其他值。

      如果我使用 stdbool bool/_Boolfalsetrue 编写相同的示例,那将没有任何意义。因为那时编译器可能将代码实现为位域,并且单个位只能具有值 1 或 0。


      回想起来,使用防御性编程的一个更好的例子可能是这样的:

      typedef enum
      {
        APPLES,
        ORANGES
      } fruit_t;
      
      fruit_t fruit;
      
      if(fruit == APPLES)
      {
        // ...
      }
      else if(fruit == ORANGES)
      {
        // ...
      }
      else
      {
        // error
      }
      

      【讨论】:

      • 所以如果变量是1位,就没有办法在“else”中跳转?
      • @TrieuTheVan 不是因为该变量的内存损坏,不是。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-16
      • 1970-01-01
      • 2020-02-25
      • 2013-10-19
      • 1970-01-01
      • 1970-01-01
      • 2014-10-05
      相关资源
      最近更新 更多