C passou seus primeiros 27 anos sem um tipo booleano. Isso não é um descuido - é uma decisão de projeto com consequências que você encontra no seu primeiro dia, e um bug famoso que decorre diretamente dela.
Zero é falso, todo o resto é verdadeiro
Não existe um valor especial de verdadeiro/falso nas regras de condição do C. if, while, for, &&, || e ! fazem exatamente uma pergunta: este valor é zero?
Números negativos são verdadeiros. Frações são verdadeiras. Um ponteiro não nulo é verdadeiro. Apenas zero - em qualquer tipo - é falso.
É por isso que o código C é cheio de idiomas que parecem lacônicos até você conhecer a regra:
if (count) /* se count não for zero */
if (!count) /* se count for zero */
if (p) /* se p não for NULL */
if (!strcmp(a, b)) /* se as strings forem iguais - strcmp retorna 0 quando casam */
Esse último pega as pessoas. strcmp retorna 0 quando as strings casam, então "igual" se lê como "não" dentro de um if. Escrever if (strcmp(a, b) == 0) é mais claro e faz exatamente a mesma coisa.
Comparações produzem int
Uma comparação em C não é um tipo especial de expressão. Ela avalia para um int comum com o valor 1 ou 0, e você pode imprimi-la, armazená-la ou fazer aritmética com ela:
O operador ! funciona da mesma forma: produz 1 se seu operando for zero e 0 caso contrário. Então !!x é um idioma clássico que normaliza qualquer valor para exatamente 0 ou 1.
bool, true e false (C99)
C99 adicionou um tipo booleano de verdade chamado _Bool, e um cabeçalho que lhe dá um nome legível:
<stdbool.h> é minúsculo: ele define bool como _Bool, true como 1 e false como 0. É o cabeçalho inteiro. Mas usá-lo torna a intenção visível de um jeito que int flag = 1; nunca faz.
_Bool tem um comportamento genuinamente distinto: ele armazena apenas 0 ou 1. Atribuir qualquer valor diferente de zero converte para exatamente 1:
Ainda não existe um %b no printf para booleanos - imprima-os com %d, ou converta para texto você mesmo:
No C23 isso ficou ainda mais simples: bool, true e false viraram palavras-chave de verdade, então o include não é mais obrigatório. A maior parte do código hoje ainda mira C17, então mantenha o #include <stdbool.h>.
Retornando um booleano de uma função
É aqui que bool justifica seu lugar de forma mais clara:
Um tipo de retorno bool diz a quem chama o que o valor significa. Um retorno int de uma função chamada check_file poderia ser um booleano, uma contagem ou um código de erro - e nas convenções das bibliotecas C é frequentemente o terceiro, em que 0 significa sucesso. Nomear o tipo elimina o palpite.
O bug de = vs ==
Como a atribuição é uma expressão que resulta no valor atribuído, e como qualquer valor diferente de zero é verdadeiro, isto compila:
Duas coisas deram errado de uma vez. A condição atribuiu 5 a x, destruindo seu valor, e então testou 5, que é diferente de zero, então o ramo executou. O programa não tem erro nem aviso por padrão.
Pior, a versão com 0 nunca executa seu ramo:
if (found = 0) { /* nunca executa - e found agora é 0 */ }
Três defesas, em ordem de utilidade:
Compile com avisos. -Wall pega isso:
warning: suggest parentheses around assignment used as truth value
Chegar a zero avisos é a verdadeira solução aqui, e custa uma flag.
Escreva a constante primeiro. A "condição Yoda" transforma o erro de digitação em um erro de compilação:
if (5 == x) /* correto */
if (5 = x) /* erro: não é um lvalue - o compilador te impede */
Alguns times adoram isso e outros acham ilegível; qualquer posição é defensável, mas funciona.
Seja deliberado quando for intencional. A atribuição dentro de uma condição é genuinamente útil para ler entrada:
int c;
while ((c = getchar()) != EOF) { ... }
Os parênteses extras em torno de c = getchar() são exigidos por precedência, e também sinalizam que a atribuição é intencional. Esse é o idioma - se a sua atribuição dentro de uma condição não se parece com isso, provavelmente é um erro de digitação.
Erros comuns com booleanos
Comparar com true. if (flag == true) funciona com stdbool.h, mas quebra no momento em que flag é um int guardando 42, porque 42 == 1 é falso. Escreva if (flag).
Comparações encadeadas. if (0 < x < 10) é sempre verdadeiro: 0 < x resulta em 0 ou 1, e ambos são menores que 10. Escreva if (x > 0 && x < 10).
Bit a bit em vez de lógico. & e | não são && e ||. Eles não fazem curto-circuito, e combinam bits:
Ambos os operandos são "verdadeiros" e, ainda assim, a & b é 0. Usar & onde você queria && produz uma condição que só está errada para certos valores, que é o tipo de bug mais difícil de encontrar.
Comparar floats por igualdade. if (0.1 + 0.2 == 0.3) é falso. Compare contra uma tolerância - veja tipos de dados para entender o porquê.
Perguntas frequentes
C tem um tipo booleano?
Desde o C99, sim: _Bool é um tipo embutido, e incluir <stdbool.h> te dá as grafias mais amigáveis bool, true e false. Antes do C99 não havia booleano nenhum - os programas usavam int com 0 para falso e 1 para verdadeiro, o que ainda funciona e ainda é comum.
O que conta como verdadeiro em C?
Qualquer valor diferente de zero. 1, -5, 0.01 e um ponteiro não nulo são todos verdadeiros em uma condição; apenas 0, 0.0 e um ponteiro nulo são falsos. Não existe uma verificação booleana separada - if (x) simplesmente testa se x é diferente de zero.
Como uso bool em C?
Adicione #include <stdbool.h> e então bool ready = true; funciona como você esperaria. Por baixo dos panos bool é _Bool, que armazena apenas 0 ou 1 - atribuir qualquer valor diferente de zero a ele guarda exatamente 1.
Qual é a diferença entre = e == em C?
= atribui, == compara. if (x = 5) atribui 5 a x e então testa 5, que é diferente de zero, então o ramo sempre executa. if (x == 5) testa igualdade. O primeiro compila sem erro, que é por que -Wall e o hábito de escrever if (5 == x) existem.