Bitwise em C++

Desenvolvimento 16 de Novembro de 2019 às 15:07

A linguagem C é conhecida mundialmente até os dias atuais, utilizada para softwares que necessitam de funcionalidades mais baixas ou desenvolvimento de sistemas operacionais. A linguagem foi desenvolvida nos anos de 1970 por um dos criadores do Unix e utilizada para modernizar o próprio sistema operacional que anteriormente era escrito em assembly. Posteriormente a linguagem serviu de base para a criação da linguagem C++.

A linguagem C++ ou CPP é reconhecida por oferecer diversas possibilidades de desenvolvimento, podendo utilizar paradigmas de programação distintos, como o estrutural e o orientado a objetos conforme a sua evolução, contudo ela carrega em seu núcleo a base da linguagem C dando possibilidades de trabalhar em um nível mais baixo de desenvolvimento como operadores lógicos bitwise.

Bit a bit

Os operadores lógicos bitwise significam bit a bit, sendo possível a aplicação de portas lógicas como, AND (E), OR (OU Inclusivo), XOR (OU Exclusivo) e NOT (Inversor), como também deslocamento a direta ou a esquerda de bits. A tabela abaixo apresenta os operadores bitwise do C++ e suas operações.

Operador Operação
& AND (Interseção booleana)
| OR (União booleana)
^ XOR (Diferença simétrica booleana)
<< Deslocamento a esquerda
>> Deslocamento a direta
~ NOT (Inversor booleano ou complemento)

As operações na linguagem C++ são escritas de uma forma simples e lógica, os exemplos de código abaixo demonstram alguns trechos de operações bitwise. Tente substituir as variáveis b, c, d com 0 ou 1 para obter resultados diferentes.

// Valores: b = 1, c = 0, d = 0

a = b | c;         // a = 1

a = (b & c);       // a = 0

a = (b ^ c);       // a = 1

a = ~(b & c);      // a = 1

a = (b & (d | c)); // a = 0

A Figura 1 mostra a operação d = c & b, sendo que os valores dos 2 conjuntos de 8 bits então passando por uma operação AND ou interseção booleana, formando um novo conjunto de bits ou saída do bitwise.

Figura 1: Bitwise da operação d = c & b com dois conjuntos de 8 bits passando pelas portas AND e formando um novo conjunto de bits.

O conjunto de bits na linguagem é interpretado como um vetor, utilizando a constante bitset<x>, a variável x é o tamanho do conjunto de bits, podendo ser 4 bits, 8 bits, 16 bits ou 32 bits, para utilizar essas funções é necessário incluir a biblioteca bitset. O código em C++ abaixo exemplifica o bitwise da Figura 1.

#include <iostream>
#include <bitset>

using namespace std;

int main(int argc, char** argv)
{
	bitset<8> d, c, b;
	
	c = 0b11101111; // 1 conjunto em binário
	b = 0b10010001; // 2 conjunto em binário

	d = (c & b);
	
	cout << d; // Resultado = 10000001
	
	system("PAUSE");
	return 0;
}

As operações de deslocamento de bits são realizadas com a contagem bit a bit da seguinte forma:

  • shift-expression << additive-expression
  • shift-expression >> additive-expression

O deslocamento (<<) fazem os bits da shift-expression se moverem para a esquerda pelo número de posições do additive-expression. Exemplo: O número 4 em binário é 00000100, aplicando um deslocamento de 2 posições para a esquerda será obtido 00010000 e as posições percorridas serão preenchidas com 0, posteriormente convertendo o resultado para decimal dará 16, ou seja, deslocamentos para a esquerda são equivalentes a uma multiplicação, conhecido como the power of two.

#include <iostream>
#include <bitset>

using namespace std;

int main(int argc, char** argv)
{
	bitset<8> bits;
	
	bits = 0b00000100; // 4
	
	bits = (bits << 2);
	
	cout << bits << endl; // Resultado = 00010000 ou 16
	
	system("PAUSE");
	return 0;
}

Outra forma de deslocamento para a esquerda é setar o valor 1 no shift-expression ou adicionar o operador OR, sendo assim as posições percorridas não serão mais substituídas por 0, permitindo guardar as posições originais do conjunto modificado.

#include <iostream>
#include <bitset>

using namespace std;

int main(int argc, char** argv)
{
	bitset<8> bits, bits2;
	
	bits  = 0b01100110; // 1 conjunto em binário
	bits2 = 0b01100110; // 2 conjunto em binário
	
	bits   = (1 << 4)|(1 << 3);
	bits2 |= (1 << 4)|(1 << 3);
	
	cout << bits << endl;  // Resultado = 00011000
	cout << bits2 << endl; // Resultado = 01111110
	
	system("PAUSE");
	return 0;
}

O deslocamento (>>) fazem os bits da shift-expression se moverem para a direita pelo número de posições do additive-expression. Exemplo: O número 22 em binário é 00010110, aplicando um deslocamento de 1 posição para a direita será obtido 00001011 e convertendo o resultado para decimal dará 11. Exemplo 2: O número 5 em binário é 00000101, aplicando um deslocamento de 1 posição para a direita será obtido 00000010 e convertendo o resultado dará 2, ou seja, deslocamentos para a direita são equivalentes a uma divisão por inteiro.

#include <iostream>
#include <bitset>

using namespace std;

int main(int argc, char** argv)
{
	bitset<8> bits1, bits2;
	
	bits1 = 0b00010110; // 22
	bits2 = 0b00000101; // 5
	
	bits1 = (bits1 >> 1);
	bits2 = (bits2 >> 1);
	
	cout << bits1 << endl; // Resultado = 00001011 ou 11
	cout << bits2 << endl; // Resultado = 00000010 ou 2
	
	system("PAUSE");
	return 0;
}

Entre outras aplicações, conforme o livro de Stroustrup a técnica bitwise é profundamente utilizada nas funções internas da linguagem, como na implementação da biblioteca do C++ ostream, como mostra o exemplo abaixo:

enum ios_base::iostate
{
    goodbit=0, eofbit=1, failbit=2, badbit=4
};

// Setando e testando o estado do ostream
state = goodbit;
// ...
if (state&(badbit|failbit)) // Será 0

// Uma função que atinge o end-of-input pode ser representado assim:
// Sendo que todos os bits serão setados como 1.
state |= eofbit;

Referências

  1. RUSSELL, D. Introduction to embedded systems: using ANSI C and the Arduino development environment. 1 ed. Morgan & Claypool, 2010.
  2. STROUSTRUP, B. The C++ programming language. 4 ed. Addison-Wesley, 2013.