środa, 16 listopada 2022

C - Typ Bool

W tym poście chciałbym opisać działanie typu bool.


Typ bool jest typem logicznym przyjmującym wartości true = 1 lub false = 0.

_Bool:


Jest typem logicznym zdefiniowanym w standardzie C99. Definiuje on wartości 1 jako TRUE i 0 jako FALSE. W standardowych bibliotekach nie ma definicji TRUE oraz FALSE, można je przygotować wykorzystując define. 

  1. #include <stdio.h>
  2.  
  3. #define TRUE 1
  4. #define FALSE 0
  5.  
  6. int main()
  7. {
  8.     _Bool a = TRUE;
  9.    
  10.     if (TRUE == a)
  11.     {
  12.         printf("TRUE\r\n");
  13.     }
  14.     else if (FALSE == a)
  15.     {
  16.         printf("FALSE\r\n");
  17.     }
  18.     else
  19.     {
  20.         printf("DIFF\r\n");
  21.     }
  22.  
  23.     return 0;
  24. }

Należy pamiętać przy takiej deklaracji, że wpisanie d zmiennej _Bool wartości np. -200, +800 spowoduje, że wyrażenie będzie zawsze prawdziwe (TRUE). Wartość false zostanie osiągnięta tylko, gdy zmienna a dostanie wartość 0.

  1. _Bool a = -200; //Będzie 1, TRUE
  2. _Bool a = 30; //Będzie 1, TRUE
  3. _Bool a = 0.45f; //Będzie 1, TRUE

Należy o tej zasadzie pamiętać w przypadku instrukcji warunkowych if. Jeśli nie stosujemy odpowiednich operacji logicznych (co oczywiście powinnyśmy robić) to wartości inne niż 0 będą zawsze prawdziwe:

  1. int a = 33;
  2. if(value) { //TRUE
  3.     printf("value true\r\n");
  4. }
  5.    
  6. value = -44;
  7. if(value) { //TRUE
  8.     printf("also true\r\n");
  9. }

bool:


Jest to definicja wartości logicznej wprowadzona w plikach <stdbool.h>. Typ bool jest właściwie makrem które wykorzystuje typ _Bool:

  1. #ifndef _STDBOOL_H
  2. #define _STDBOOL_H
  3.  
  4. #ifndef __cplusplus
  5.  
  6. #define bool        _Bool
  7. #define true        1
  8. #define false        0
  9.  
  10. #else /* __cplusplus */
  11.  
  12. /* Supporting <stdbool.h> in C++ is a GCC extension.  */
  13. #define _Bool        bool
  14. #define bool        bool
  15. #define false        false
  16. #define true        true
  17.  
  18. #endif /* __cplusplus */
  19.  
  20. /* Signal that all the definitions are present.  */
  21. #define __bool_true_false_are_defined        1
  22.  
  23. #endif        /* stdbool.h */

Dodatkowo plik wprowadza także wartości true oraz false. Wobec tego, oprócz nazwy nie ma żadnych różnic pomiędzy tymi typami. W programie będą zachowywać się identycznie.

Własny typ bool:


Jednym ze spotykanych rozwiązań jest przygotowanie własnego typu bool. Przykładowe rozwiązanie może wyglądać następująco:

  1. typedef enum { FALSE, TRUE } boolean_t;

Minusem takiego podejścia jest otrzymanie wartości innych niż tylko true and false. Ponieważ można do zmiennej przypisać wartości inne niż 0 czy 1. Oczywiście należy unikać sytuacji gdy zmienna, która ma przechowywać wynik operacji logicznej dostaje inną wartość. 

  1. typedef enum { FALSE, TRUE } boolean_t;
  2.  
  3. int main()
  4. {
  5.     boolean_t a = 44;
  6.  
  7.     if (TRUE == a)
  8.     {
  9.        printf("true\r\n");
  10.     }
  11.     else if (FALSE == a)
  12.     {
  13.        printf("false\r\n");
  14.     }
  15.     else
  16.     {
  17.        printf("difff\r\n");
  18.     }
  19.  
  20.     return 0;
  21. }

Powyższy program nie będzie rzutował wartości na wartość true (1) tylko zapisze do zmiennej wartość 44. Co spowoduje wywołanie innej instrukcji warunkowej, niż ta, którą by została wywołana dla typów zdefiniowanych w bibliotece standardowej. 

Jeśli zależy nam na identycznym zachowaniu to trzeba zastosować funkcję bądź makro sprawdzające zapisywanie wartości do zmiennej, lub odczytującą wartości ze zmiennej.

  1. #define CHECK_BOOL(value)   ((value) == 0) ? 0 : 1
  2.  
  3. boolean_t checkBoolValue(boolean_t val)
  4. {
  5.     if(val == 0)
  6.     {
  7.         return 0;
  8.     }
  9.     return 1;
  10. }
  11.  
  12. //...
  13. //...
  14.  
  15. boolean_t a = checkBoolValue(44);// => 1
  16. boolean_t a = CHECK_BOOL(1);// => 1
 
Bądź uchronić się przed tym wprowadzając jasno zdefiniowane warunki sprawdzajace:

  1. if(a == 1) //if(a == TRUE)
  2. if(a == 0) //if(a == FALSE)
  3. else{
  4.     //rób to samo co dla a == 1
  5. }

Jest to trochę niewygodne, i może przysporzyć pewnych problemów. Alternatywą jest stosowanie następującej definicji:

  1. struct boolean {
  2.     uint8_t value : 1;
  3. };

W związku z tym, że jest tu wykorzystane pole bitowe. To kompilator pozwoli na wpisanie wartości 1 lub 0. Ponieważ struktura zawiera jedną zmienną nie będzie to miało oczywiście wpływu na jej rozmiar zdefiniowany w pamięci. 

Minusem w tym przypadku jest konieczność odwoływania się do zmiennej jak do struktury. 

  1. struct boolean {
  2.     uint8_t value : 1;
  3. };
  4.  
  5. struct boolean a = {55};
  6.  
  7. if (TRUE == a.value)
  8. {
  9.     printf("true\r\n");
  10. }
  11.  
  12. //....
  13. //....

Natomiast ograniczy to wartości zmiennych do 0 i 1.

Osobiście nie widzę żadnej zalety stosowania własnego typu bool (chyba, że jest to z jakichś powodów jedyna możliwość). Więc najbezpieczniej posłużyć się wartościami opisanymi w bibliotekach standardowych.