W tym artykule pokażę pewne niuanse związane z typem bool w C. Wartości tego typu są wynikami operacji logicznych i warunkami w ifach i pętlach. Jednak bool nie jest do końca pełnoprawnym typem, a raczej intem w przebraniu. Sytuację próbowano poprawić w standardzie C99, ale teraz problem jest taki, że w różnych kontekstach bool może zachowywać się inaczej zależnie czy korzystamy z boola C99 czy własnego.

Własny bool

Na początku w C nie było czegoś takiego jak typ bool. Trzeba było używać w tym celu liczb całkowitych. I tutaj dochodzimy do dużego problemu takiego rozwiązania, bo o ile typ bool ma dwie możliwe wartości – true i false – to nawet 8-bitowy typ całkowity ma ich 256, a często bool jest reprezentowany jako liczba 32-bitowa.

Dlatego więc boola używamy według dwóch różnych konwencji. Pierwsza z nich jest wyrażona w typowej definicji własnego typu bool:

typedef uint8_t bool;

#define false 0
#define true (!false)

W tym wypadku tak naprawdę false jest zerem, a true jedynką. Mamy jednak jeszcze drugą konwencję, która jest przydatna w innych wypadkach:

if(ptr) //check if ptr not NULL
{

}

False to zero, a true to każda inna wartość. Dzięki temu można w uproszczony sposób zapisać warunek w ifie albo pętli. Jak w powyższym przykładzie sprawdzającym, czy wskaźnik nie jest nullem i wykorzystującym, że NULL ma wartość 0, a poprawny adres w pamięci musi mieć inną wartość.

I teraz problem polega na tym, że dla kompilatora nie ma czegoś takiego jak bool i nie wie, że każda wartość różna od zera ma być traktowana jako true.

Zobaczmy taki przykład:

bool val = 2;

if (true == val)
{
    //do something
}

if (false != val)
{
    //do something
}

Załóżmy, że z jakiegoś powodu nasza zmienna typu bool przechowuje wartość inną niż 0 albo 1. Jeżeli bool jest pod spodem intem możemy tak zrobić przez nieuwagę i nawet nie zauważyć. W tym wypadku dwa ify reprezentujące logicznie to samo dają inne wyniki.

Zdarzyło mi się kiedyś debugować tego typu błąd w kodzie produkcyjnym. To zachowanie jest tak nieintuicyjne, że naprawdę ciężko się domyślić. Jednak oczywiście prawdziwym źródłem błędu jest zawsze miejsce przypisania do boola wartości innej niż 0 lub 1.

Bool z C99

Typ bool został wprowadzony do standardu C99 i nie jest zwykłym typedefem na inta. Teraz mamy oddzielny typ o nazwie _Bool a definicje true, false i bool możemy dodać za pomocą headera <stdbool.h>.

Nowy bool wprowadza jeszcze jedną ważną zmianę – każda wartość inna niż 0 jest traktowana jako true. Tak więc z boolem C99 powyższy przykład z ifami będzie działać tak jak się spodziewamy.

Nowy bool ma również nieco inne zasady rzutowania:

bool val = (bool)0.3f;

Jeżeli bool jest traktowany jako int, to zmienna val przyjmie wartość 0 zgodnie z zasadami rzutowania float na int. Natomiast dla boola C99 przyjmie wartość 1, bo 0.3f jest różne od 0.0f.

Jak używać boola w praktyce?

Jak już wspomniałem wcześniej – język C ma pewne niedoskonałości i programista musi je zwalczać trzymając się odpowiednich praktyk. Aby maksymalnie ograniczyć ilość błędów z boolem warto stosować się do praktyki opisanej w MISRA C nazwanej tam „essential types”. Tyczy się ona nie tylko boola, ale również enuma, znaków i innych typów nie mających pełnego wsparcia w C.

Chodzi o to, że poszczególne typy zmiennych są przeznaczone do jakiś konkretnych operacji. Na przykład arytmetykę powinniśmy robić na floatach albo liczbach całkowitych (najlepiej ze znakiem), operacje znakowe na charach, a operacje logiczne na boolach, a bitowe na intach bez znaku.

Jeżeli nie będziemy przypisywać do zmiennych typu bool wyników innych operacji, nie będziemy robić na nich obliczeń arytmetycznych czy bitowych, zmniejszamy szansę na powstanie niebezpiecznych sytuacji.

Po drugie powinniśmy jasno określić, czy możemy korzystać z boola C99 i wtedy nie deklarujemy własnego typu, bo w ten sposób możemy sobie tylko narobić problemów.

Tak samo warto zwrócić uwagę na symbole zdefiniowane w używanych bibliotekach, czy innych modułach. Możemy mieć różne definicje w różnych miejscach i doprowadzić do sytuacji, kiedy nie wiemy, czy użyć true, True, czy TRUE. Albo czy zmienną zadeklarować jako bool, boolean, czy my_bool_t.

Na koniec jeszcze zachęcam do przeczytania wypowiedzi Linusa Torvaldsa na temat typu bool z C99 w Linuxie. Twierdzi on, że typ bool z C99 nie daje zbyt wielu korzyści, a jego szczegółowe działanie nie jest dobrze znane przez wielu programistów C. Przez to bool C99 również może być przyczyną błędów. Dlatego Torvalds jest zwolennikiem nie używania typu bool w ogóle i po prostu korzystania z inta.

Jeżeli chcesz dowiedzieć się więcej o tym, jak pisać dobry kod w C – przygotowuję właśnie szkolenie online “C dla zaawansowanych”. Wejdź na https://cdlazaawansowanych.pl/ i zapisz się na mój newsletter. W ten sposób informacje o szkoleniu na pewno Cię nie ominą.