18 Jan
Quando estamos programando, é muito comum precisarmos de um tipo booleano. Infelizmente o padrão Ansi C (isoc89) não possui um tipo primitivo para representar verdadeiro ou falso.
Qual a solução? Dentre várias, uma das mais perigosas e mais comum é essa:
typedef int bool;
#define TRUE 1
#define FALSE 0
Primeiro, como ela funciona:
Em C, como não existe boolean, qualquer inteiro diferente de 0 é considerado como verdadeiro e 0 é considerado falso. Portanto, nada mais lógico que pegar 0 e definir como falso e um outro número qualquer e definir como verdadeiro.
Qual o problema? Em 95% das aplicações que você normalmente faz, isso é uma solução perfeita, porque você sempre usa os operadores booleanos que encerram em curto-circuito (&& e ||).
A coisa muda totalmente de figura quando você usa os operadores bitwise (&, | e ^) para gerar condições booleanas. Isso é comum quando estamos usando funções que possuem efeitos colaterais (isso não é considerado uma prática muito boa, mas tem muita gente que usa isso ainda).
Imagine que você está usando uma função sua que produz efeitos colaterais e uma de uma biblioteca qualquer que também têm efeitos colaterais. As duas devolvem um inteiro diferente de 0 se bem sucedidas.
Como você é uma pessoa disciplinada, para garantir a consistência do seu sistema, quando você quer valores booleanos, você só atribui TRUE ou FALSE.
Se o código for esse, sem problemas:
if (sua_funcao() && funcao_da_lib())
faz_algo();
Mas isso é ruim, pois você precisa que as duas funções sejam executadas (mesmo se a sua falhar). O que você faz?
if (sua_funcao() & funcao_da_lib())
faz_algo();
Arranca um & de lá e passa 20 horas debuggando. Porquê? Simples. Por algum motivo estranho, o cara que escreveu a biblioteca que você está usando resolveu devolver 2 como verdadeiro. Olha que legal que fica se você substitui os valores devolvidos (em caso de sucesso) no lugar das funções:
if (1 & 2)
faz_algo();
Como dois não é impar, a função faz_algo() não será executada (causando uma grande dor de cabeça em você).
Mesmo você sendo o programador mais disciplinado da face da terra, não dá pra evitar problemas assim. Não dá pra ter controle sobre o código dos outros.
Para evitar esse tipo de problemas, eu costumo definir minhas constantes assim:
#include <limits.h>
typedef unsigned int bool;
#define TRUE UINT_MAX
#define FALSE 0
Isso protege contra todos os problemas? Não. Mas evita alguns que podem dar muita dor de cabeça. Isso porque tendo todos os bits setados como 1, quando usar o operador bitwise & ele só vai dar false se o outro valor não possuir nenhum bit setado para 1 (que é exatamente o que queremos).
Mas ainda bem que no padrão isoc99 existe o tipo bool. Vai evitar muitos problemas. Mas até ele ser realmente adotado ainda vai algum tempo.
Posts Relacionados:
6 Responses for "TRUE & TRUE == FALSE ?"
Olá, Jonas!
Não entendi o que você quis dizer com “operadores booleanos que encerram em curto-circuito”.
Acho que um jeito mais legível de fazer isso (apesar de menos econômico) é atribuir o valor retornado de cada função em uma variável e depois testar a condição.
Olá Luiz,
Os operadores que encerram em curto circuito (&& e ||) param de executar a condição booleana quando o resultado ja é previsível. Por exempĺo:
if ( 0 && funcao_que_sempre_devolve_true())
nessa linha a funcao_que_sempre_devolve_true() não será executada, porque FALSE AND qualquer coisa da FALSE. É uma espécie de otimização. Não fazer nada que seja inútil. Por isso que dá pra fazer:
if (p != NULL && p->cont == 4)
Sem correr risco de uma SegFault.
E com certeza o jeito mais legível é você atribuir para variáveis, mas a gente não tem controle sobre quem vai usar nosso código. O cara pode não fazer isso e perder muito tempo debuggando, especialmente por ser algo meio incomum.
Pessoalmente não gosto de criar tipo ou macro para true e false, prefiro 0 e 1.
Entretando uma dificuldade estou tendo:
Se minha variavel vai conter somente 0 (false) ou 1 (true), acho desperdicio cria-la como int, um opção é cria-la como unsigned shot int (para economizar).
Pesquisando achei:
unsigned FazAlgumaCoisa:1; /* ocupa 1 bit */
unsigned GravaNumero:5; /* ocupa 5 bit */
Mas só consegui compilar isso estando dentro de uma estrutura, você sabe por que… é a melhor opção para armazenar valores logicos ?
Olá Francis,
Você precisa manter isso dentro de um struct porque o que você está fazendo é um mapeamento de bits. Como os tipos primitivos tem um tamanho de bits definidos, você estaria limitado a, por exemplo, 32 bits. Pra simplificar na linguagem, essa estrutura de mapeamento só é permitida em uma estrutura, pois ela tem tamanho variável. Se você quiser mapear 33 bits, você pode (em um int de 32 não poderia). O problema dessa estrutura é que nunca é que em nenhum sistema operacional (talvez exista algum…) você pode manipular diretamente apenas um bit. O mínimo que você consegue manipular é um byte. Portanto você vai gastar pelo menos um ciclo de processamento a mais para usar essa estrutura de manipulação de bits sempre que for ler ou escrever um valor (você precisa calcular o valor final do byte e depois gravá-lo). Além disso, o compilador faz várias otimizações e sempre que ele vai fazer uma conta com um tipo menor que um inteiro ele promove esse tipo a inteiro. Seu short int, no fundo, vai ser um int na hora de ser usado. A struct, mesmo que tenha apenas um bit mapeado, vai ter no mínimo 4 bytes (tamanho mínimo de estruturas em C).
Essa é uma economia que não vale muito a pena tentar fazer. A grande vantagem do mapeamento de bits é facilitar a leitura ao mesmo tempo que comprime os dados (muito bom para trabalhar com redes)
Mas no fundo não existe uma melhor forma de armazenar um valor lógico. vai depender do uso e do gosto do programador.
Jonas…Só não entendi isso:
“Como dois não é impar, a função faz_algo() não será executada (causando uma grande dor de cabeça em você).”
poderia explicar melhor ?
Olá Rodrigo,
Faltou uma pequena introdução à manipulaçãode bits no post. Em binário, 2 é representado por 10 e 01 é um mesmo.
O operador & funciona da seguinte forma:
1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0
Agora se você emparelhar os bits e aplicar a regra acima terá o seguinte resultado:
1 0
0 1 &
——
0 0
Como o bit que determina se o número é impar é o último, para saber se é impar a única coisa que você precisa é aplicar o operador & com o número 1. Se n & 1 == 0, significa que o último bit do número era 1 e consequentemente o número é impar.
Naquele caso, como dois não era impar (não tinha o último bit marcado como 1), aquela conta resultará em 0 (falso em C) e a função não será executada (e isso é bem complicado de detectar quando debugando ;))
Leave a reply