Trilha de aprendizado · Nível 9 · Tutorial 10

Máscaras e operações de bits em C/C++

Manipule flags e campos de bits em variáveis e combine ou separe bytes, respeitando as promoções e os limites dos tipos do Uno R3.

  • Nível: Intermediário
  • Duração: 25 min
  • 9 passos
Máscaras e operações de bits em C/C++

O que você vai percorrer

  1. Localize os bits de uma máscara Reconheça uma máscara hexadecimal como um mapa das posições de um byte que ela seleciona. 2 min
  2. Diferencie operações lógicas e bit a bit Compare operadores que combinam padrões de bits com operadores que avaliam condições booleanas. 2 min
  3. Considere as promoções antes de complementar Entenda a promoção de `uint8_t`, o alcance do complemento e a limitação explícita de resultados antes de armazená-los em um byte. 3 min
  4. Consulte flags sem alterar a variável Use máscaras e comparação explícita com zero para consultar flags sem modificar a variável. 2 min
  5. Ative, limpe e inverta bits selecionados 3 min
  6. Desloque com segurança e construa máscaras Construa máscaras de um bit com deslocamentos válidos, distinguindo a largura de unsigned int da faixa de um byte. 3 min
  7. Leia um campo de bits Isole um campo contíguo de um byte, alinhe-o ao bit zero e preserve o valor original. 2 min
  8. Combine e recupere dois bytes Componha uma palavra de 16 bits a partir de dois bytes e recupere exatamente os valores originais. 4 min
  9. Aplicação final: confira as máscaras e os resultados Resolva uma sequência integrada de manipulação de bits e confira limites, tipos, preservação de flags e recuperação de bytes. 4 min

O que você vai aprender

  • Construir uma máscara para consultar ou modificar uma posição de bit.
  • Distinguir uma operação lógica de uma operação bit a bit.
  • Verificar o tipo e a quantidade de posições antes de executar um deslocamento.
  • Combinar dois bytes em uma palavra de 16 bits e recuperar os bytes originais.
  • Rastrear os resultados em hexadecimal e confirmar quais bits foram preservados.

Antes de começar

  • Cálculos e conversões numéricas em C/C++
  • Decisões com if e else no firmware
  • Representação binária e hexadecimal

Passo 1 de 9

Localize os bits de uma máscara

Reconheça uma máscara hexadecimal como um mapa das posições de um byte que ela seleciona.

Leia uma máscara como mapa

Posições e seleção

Um byte tem oito posições, numeradas de 0 a 7. A posição 0 é o bit menos significativo (à direita); a 7 é o mais significativo (à esquerda).

Uma máscara é um valor inteiro usado para marcar posições de interesse: cada bit em 1 seleciona sua posição correspondente. Por enquanto, apenas leia a máscara: ela não muda nenhuma variável sozinha.

Um byte visto como máscara

Diagrama de oito bits numerados de 7 a 0, com os bits 7 e 0 destacados no padrão 10000001.

Na máscara 0x81, os bits 7 e 0 estão em 1; portanto, essas são as posições selecionadas.

Literal hexadecimal no código

O prefixo 0x informa que o literal está em hexadecimal. O tipo uint8_t armazena os oito bits da máscara.

cpp
#include <stdint.h>

uint8_t extremos = 0x81;  // bits 7 e 0 em 1
uint8_t centro   = 0x3C;  // bits 5, 4, 3 e 2 em 1

Associe padrões e posições

Do hexadecimal às posições

Compare o padrão de oito bits com seu hexadecimal. Bits em 0 não são selecionados; bits em 1 são selecionados.

Por exemplo, 0x24 corresponde a 0010 0100: seleciona as posições 5 e 2. A máscara é somente um padrão de seleção; as operações que a aplicam serão estudadas depois.

Pense antes de associar

Associe cada máscara às posições que ela seleciona.

Associe máscara e posições

Agora faça as associações.

Máscaras fixas

Relacione cada literal às posições cujos bits valem 1.

Toque em um item e depois no par correspondente.

Verifique sua leitura

Pratique a escrita

Complete a declaração com o literal que seleciona simultaneamente as posições 7 e 0 de um byte. Use o prefixo hexadecimal 0x.

Declare a máscara dos extremos

Preencha a máscara hexadecimal.

cpp
#include <stdint.h>

uint8_t mascaraExtremos = ;

Passo 2 de 9

Diferencie operações lógicas e bit a bit

Compare operadores que combinam padrões de bits com operadores que avaliam condições booleanas.

Forme um novo padrão de bits

Bit a bit: uma coluna por vez

Os operadores bit a bit trabalham posição por posição entre dois valores inteiros. Para cada coluna de bits, eles geram outro bit:

  • & resulta em 1 somente quando os dois bits são 1.
  • | resulta em 1 quando pelo menos um bit é 1.
  • ^ resulta em 1 quando os bits são diferentes.

Com a = 0x30 (00110000) e b = 0x0C (00001100), não há nenhuma posição em que ambos tenham 1. Portanto, a & b vale 0x00 — mesmo que a e b sejam valores não nulos.

Rastreamento de dois bytes

Diagrama com os bytes 00110000 e 00001100 alinhados por posição e os resultados das operações AND, OR e XOR.

Alinhe as posições antes de operar. Neste caso, 0x30 & 0x0C = 0x00, enquanto 0x30 | 0x0C = 0x3C e 0x30 ^ 0x0C = 0x3C.

Escolha pelo resultado desejado

O papel dos zeros e uns da máscara

Uma máscara indica quais posições importam no resultado. Se mascara tem 1 em uma posição:

  • valor & mascara mantém o bit de valor naquela posição; onde a máscara tem 0, o resultado fica 0.
  • valor | mascara produz 1 naquela posição; onde a máscara tem 0, o resultado acompanha valor.
  • valor ^ mascara produz o bit oposto naquela posição; onde a máscara tem 0, o resultado acompanha valor.

Essas expressões produzem valores inteiros. Elas não são, por si só, perguntas verdadeiras ou falsas.

Bit a bit não é lógico

Compare o objetivo antes de escolher o operador.

Os operadores usam símbolos parecidos, mas têm resultados e regras de avaliação diferentes.
ExpressãoResultadoUso
`a & b`Padrão de bits inteiroCombinar bits correspondentes
`a | b`Padrão de bits inteiroCombinar bits correspondentes
`a ^ b`Padrão de bits inteiroMarcar posições diferentes
`a && b``true` ou `false`Exigir dois valores verdadeiros
`a || b``true` ou `false`Aceitar ao menos um valor verdadeiro

Padrão versus condição

Compare os tipos de resultado destas duas intenções.

cpp
uint8_t a = 0x30;
uint8_t b = 0x0C;

uint8_t bitsEmComum = a & b;  // 0x00
bool ambosSaoNaoNulos = a && b; // true

Considere também a avaliação

Atenção ao curto-circuito

Em uma expressão bit a bit, os dois operandos são avaliados para que seus bits possam ser combinados. Já && e || usam curto-circuito:

  • Em testeA() && testeB(), se testeA() for falsa, testeB() não é chamada.
  • Em testeA() || testeB(), se testeA() for verdadeira, testeB() não é chamada.

Assim, não troque & por && apenas porque ambos lembram AND. Além de retornarem tipos de resultado diferentes, a execução das funções nos operandos pode mudar.

Avaliação dos operandos

Nas duas primeiras linhas, as duas funções são chamadas. Nas demais, a segunda chamada depende do resultado da primeira.

cpp
bool testeA();
bool testeB();

bool padrao = testeA() & testeB(); // chama as duas
bool outroPadrao = testeA() | testeB(); // chama as duas

bool condicao1 = testeA() && testeB(); // pode pular testeB()
bool condicao2 = testeA() || testeB(); // pode pular testeB()

Dica

Regra prática

Use operadores lógicos para combinar condições de if. Use operadores bit a bit quando precisar obter ou rastrear um padrão de bits inteiro.

Pratique a escolha do operador

Complete e rastreie

Complete cada expressão com o operador adequado. Depois, confira: comuns é 0x00; unidos e diferentes são 0x3C; as duas variáveis bool são verdadeiras.

cpp
uint8_t a = 0x30;
uint8_t b = 0x0C;

uint8_t comuns = a  b;
uint8_t unidos = a  b;
uint8_t diferentes = a  b;

bool ambosNaoNulos = a  b;
bool algumNaoNulo = a  b;

Passo 3 de 9

Considere as promoções antes de complementar

Entenda a promoção de uint8_t, o alcance do complemento e a limitação explícita de resultados antes de armazená-los em um byte.

A promoção define a largura do complemento

A expressão pode ser mais larga que o byte

No Uno R3, uint8_t armazena 8 bits, mas, em uma expressão, ele é promovido a int. Isso ocorre porque o int de 16 bits da placa representa todos os valores de 0 a 255.

Assim, se byteValor contém 0x5A, a expressão ~byteValor complementa os 16 bits do int promovido. Considerando esses 16 bits, o padrão passa de 0x005A para 0xFFA5 — não apenas de 0x5A para 0xA5.

A variável original continua com 0x5A: ~ produz um resultado, mas não altera seu operando.

Da variável ao resultado promovido

Diagrama de um byte com o padrão 01011010 sendo ampliado para uma expressão de 16 bits e complementado em todas as 16 posições.

No Uno R3, o complemento atua sobre os 16 bits do valor promovido.

Rastreamento de ~byteValor

Use esta referência para rastrear o exemplo no Uno R3.

Os padrões hexadecimais abaixo são exibidos em uma largura de 16 bits.
EtapaTipo efetivoPadrão em 16 bits
byteValor armazenadouint8_t0x5A
Valor na expressãoint0x005A
Resultado de ~byteValorint0xFFA5

Escolha entre padrão de bits e condição

Complemento de bits não é negação lógica

~ e ! não são duas formas equivalentes de negar um valor.

  • ~ complementa cada bit da largura efetiva da expressão e produz um padrão inteiro. Para 0x5A promovido no Uno R3, o padrão resultante é 0xFFA5 em 16 bits.
  • ! avalia logicamente o valor: produz false para qualquer valor diferente de zero e true para zero. Portanto, !0x5A é false.

Quando a intenção for trabalhar explicitamente com um padrão sem sinal de 16 bits no Uno R3, use um operando unsigned int, como um literal com sufixo U. O literal 0x005AU é sem sinal; seu complemento tem o padrão 0xFFA5 nessa largura.

Padrão sem sinal explícito

O sufixo U torna o literal sem sinal. No Uno R3, unsigned int tem 16 bits.

cpp
unsigned int padrao = ~0x005AU;  // 0xFFA5 no Uno R3
bool vazio = !0x005AU;            // false

Atenção

Confira a plataforma

A largura efetiva depende da plataforma e dos tipos dos operandos. Neste tutorial, os exemplos usam o Uno R3, no qual int e unsigned int têm 16 bits. Não suponha que uma expressão terá a mesma largura em toda placa ou computador.

Verifique tipo e intenção

Identifique o tipo efetivo

No Uno R3, uint8_t byteValor = 0x5A;. Qual descrição está correta para a expressão ~byteValor?

Escolha o operador lógico

Qual operador produz uma condição lógica a partir de 0x5A?

Prática: preserve somente um byte

Limite antes de armazenar

Se o objetivo é guardar somente o byte invertido, limite primeiro o resultado largo aos oito bits inferiores. Com original = 0x5A, o complemento sem sinal é 0xFFA5; a máscara 0x00FFU produz 0x00A5. Só depois represente esse valor como uint8_t.

A conversão final escolhe a representação de destino. Ela não valida automaticamente a faixa, não reduz a largura da expressão anterior e não substitui uma limitação explícita quando ela faz parte da intenção do código.

Limite o resultado antes de armazenar

Complete a máscara que mantém somente os oito bits inferiores antes da conversão para uint8_t.

Passo 4 de 9

Consulte flags sem alterar a variável

Use máscaras e comparação explícita com zero para consultar flags sem modificar a variável.

Isolar é diferente de alterar

Flags são estados em bits

Uma flag é um bit usado para representar um estado dentro de uma variável comum. Por exemplo, em estado, o bit 2 pode indicar que uma condição está ativa.

Para consultar uma flag, aplique a máscara com & e compare o resultado com zero:

(estado & mascara) != 0

A operação & produz um valor temporário. Ela não grava nada em estado; portanto, uma consulta não altera a variável.

Consulta de uma flag

Observe que somente a posição selecionada permanece no resultado temporário.

Diagrama de um byte em que o bit 2 e a máscara 0x04 estão destacados; o AND mantém apenas esse bit.

Se a posição selecionada está em 1, o resultado de estado & máscara é diferente de zero.

Uma posição pode valer mais que 1

Compare com zero, não com um

Use parênteses para tornar a intenção explícita: primeiro isole os bits com &; depois compare o resultado.

Neste exemplo, 0x04 seleciona o bit 2. Como estado vale 0x24, a consulta produz 0x04. Esse resultado não é 1, mas ainda é diferente de zero — logo, a flag está ativa.

Consulta de uma posição

Uma condição que consulta o bit 2 sem modificar estado.

cpp
#include <stdint.h>

uint8_t estado = 0x24;
const uint8_t FLAG_PRONTA = 0x04;

if ((estado & FLAG_PRONTA) != 0) {
  // A flag está ativa.
}

// estado continua valendo 0x24.

Exemplo

Exemplo rastreado

Rastreamento: 0x24 & 0x04 resulta em 0x04. A condição verdadeira vem de 0x04 != 0, e não de uma comparação com 1.

Evite escrever estado & FLAG_PRONTA == 1: além de testar o valor errado para muitas máscaras, a expressão fica ambígua para leitura. Prefira sempre ((estado & FLAG_PRONTA) != 0).

Pergunta feita por uma máscara

Pergunta feita por uma máscara

Uma máscara também pode selecionar várias posições. Em uma condição como:

(estado & ALERTAS) != 0

a pergunta é: pelo menos um dos bits selecionados está ativo?

Se nenhum estiver ativo, o AND resulta em zero. Se um ou mais estiverem ativos, o resultado é diferente de zero. A variável original permanece igual nos dois casos.

Pelo menos uma posição ativa

As duas consultas usam a mesma máscara, mas produzem respostas diferentes conforme os bits do valor.

Comparação de duas consultas com máscara de dois bits: uma resulta em valor não nulo e a outra em zero.

Uma máscara com várias posições responde se existe ao menos um bit selecionado em 1.

Consulta com duas posições

ALERTAS seleciona as posições 3 e 4. O resultado 0x10 confirma que pelo menos uma delas está ativa.

cpp
#include <stdint.h>

uint8_t estado = 0x52;
const uint8_t ALERTAS = 0x18;

if ((estado & ALERTAS) != 0) {
  // Há pelo menos um alerta ativo.
}

// 0x52 & 0x18 resulta em 0x10.
// estado continua valendo 0x52.

Verifique sua leitura

Prática: consulte sem modificar

Complete a condição que verifica FLAG_ERRO. Ela deve funcionar mesmo que o resultado isolado seja 0x20, e não 1.

cpp
#include <stdint.h>

uint8_t estado = 0x20;
const uint8_t FLAG_ERRO = 0x20;

if ((estado  FLAG_ERRO)  0) {
  // A flag está ativa.
}

Confira o valor isolado

Com estado = 0x24 e mascara = 0x04, qual resultado torna verdadeira a condição (estado & mascara) != 0?

Passo 5 de 9

Ative, limpe e inverta bits selecionados

Ative somente as flags escolhidas

Ative bits com OR

Para ativar posições de uma máscara, use OR (|). Onde a máscara tem 1, o resultado fica em 1; onde ela tem 0, o bit original é preservado.

Exemplo: 0xB2 | 0x0C resulta em 0xBE.

  • 0xB2 = 10110010
  • 0x0C = 00001100
  • 0xBE = 10111110

Só os bits 3 e 2 mudaram. A máscara não determina o valor dos demais bits: eles continuam vindo de flags.

OR preserva as posições fora da máscara

Diagrama de três bytes mostrando que o OR ativa os bits marcados pela máscara e preserva os demais.

No OR, um zero na máscara deixa passar o bit original; um um força o resultado para um.

Atualização por OR

As duas formas abaixo atualizam a própria variável. A forma composta deixa explícita a intenção de modificar flags.

cpp
const uint8_t mascara = 0x0C;

flags = static_cast<uint8_t>(flags | mascara);

// Forma equivalente para atualização:
flags |= mascara;

Limpe com AND e máscara complementada

Limpe bits sem afetar os outros

Para limpar posições, aplique AND (&) com a máscara complementada. Os 1 da máscara viram 0, forçando essas posições a zero; as outras posições devem receber 1 para serem preservadas.

No Uno R3, uint8_t é promovido a int em muitas expressões. Portanto, ~mascara não deve ser interpretado automaticamente como “o inverso de apenas oito bits”. Primeiro mantenha o cálculo em uma largura sem sinal e limite-o ao byte antes de converter.

Limpeza com limite explícito

Este padrão torna visíveis a largura intermediária e o limite de 8 bits antes do armazenamento em uint8_t.

cpp
const uint8_t mascara = 0x30;

const unsigned int manter =
    ~static_cast<unsigned int>(mascara) & 0x00FFU;

const unsigned int resultado =
    static_cast<unsigned int>(flags) & manter;

flags = static_cast<uint8_t>(resultado);

// Forma composta, quando a máscara já representa
// apenas posições do byte:
flags &= static_cast<uint8_t>(manter);

Dica

Atenção à largura efetiva

A conversão final para uint8_t representa o resultado no byte; ela não substitui a limitação explícita. Neste caso, & 0x00FFU garante que manter contenha somente os oito bits de interesse.

Compare as três atualizações

Inverta bits com XOR

Para inverter flags, use XOR (^). Onde a máscara tem 1, o bit troca: 0 vira 1 e 1 vira 0. Onde a máscara tem 0, o bit fica igual.

Assim, flags ^= mascara; é apropriado para alternar estados selecionados. Não é uma operação de consulta: ela modifica flags.

Escolha a operação pela intenção

Cada operação tem um efeito previsível sobre as posições selecionadas. Em todas elas, uma máscara limitada ao byte ajuda a manter claro quais posições podem mudar.

As formas compostas atualizam a variável à esquerda.
IntençãoExpressão explícitaForma compostaEfeito onde máscara = 1
Ativarflags | mascaraflags |= mascarafica em 1
Limparflags & ~mascaraflags &= ~mascarafica em 0
Inverterflags ^ mascaraflags ^= mascaratroca 0 e 1
Preservar——máscara = 0 preserva

Prática: modifique e confira em hexadecimal

Complete a sequência

Complete os operadores para executar, nessa ordem, ativação, limpeza e inversão.

cpp
uint8_t flags = 0xB2;

const uint8_t ativar = 0x01;
const uint8_t limpar = 0x30;
const uint8_t inverter = 0x06;

flags  ativar;
uint8_t aposAtivar = flags;

const unsigned int manter =
    ~static_cast<unsigned int>(limpar) & 0x00FFU;
const unsigned int limitado =
    static_cast<unsigned int>(flags)  manter;
flags = static_cast<uint8_t>(limitado);
uint8_t aposLimpar = flags;

flags  inverter;
uint8_t aposInverter = flags;

Registre a ativação

Depois de flags |= 0x01, qual é o valor de aposAtivar em hexadecimal?

Registre a limpeza

Após a limpeza com a máscara 0x30, qual é o valor de aposLimpar em hexadecimal?

Registre a inversão

Depois de flags ^= 0x06, qual é o valor de aposInverter em hexadecimal?

Passo 6 de 9

Desloque com segurança e construa máscaras

Construa máscaras de um bit com deslocamentos válidos, distinguindo a largura de unsigned int da faixa de um byte.

Visualize a largura e os limites

O deslocamento atua em 16 bits

Use um operando sem sinal para deixar explícita a largura em que o deslocamento ocorre. No Uno R3, unsigned int tem 16 bits. Em 1U << posicao, o literal 1U começa com um único bit em 1 e tem tipo unsigned int.

<< move os bits para a esquerda, insere zeros pela direita e descarta o que sair pela extremidade esquerda. Assim, 1U << 5 produz 0x0020. Com um operando sem sinal, >> move os bits para a direita, insere zeros pela esquerda e descarta os bits que saem à direita.

A operação acontece na largura efetiva do operando, não na largura da variável que talvez receba o resultado depois.

Movimento e extremidades

Diagrama de deslocamentos à esquerda e à direita em uma palavra sem sinal de 16 bits, mostrando zeros entrando e bits sendo descartados nas extremidades.

Os bits se movem dentro da largura efetiva do operando; os que saem dessa largura são descartados.

Validade da operação não é faixa do destino

A contagem deve ser válida antes do deslocamento: ela não pode ser negativa e deve ser estritamente menor que a largura efetiva do operando.

No Uno R3, para 1U como unsigned int de 16 bits, as contagens de 0 a 15 são válidas para a operação. Logo, 1U << 8 é uma operação válida e produz 0x0100.

Mas uma máscara destinada a uint8_t tem outra regra: só pode selecionar posições de 0 a 7. Portanto, 1U << 8 é válido em 16 bits, porém não cabe em um byte e não representa uma máscara de byte.

Valide antes de deslocar

Construa somente após validar

Para construir uma máscara de byte a partir de uma posição informada, valide primeiro o objetivo específico: a posição deve estar entre 0 e 7. Só então execute 1U << posicao.

O U em 1U é importante: ele cria o ponto de partida como unsigned int. Se o operando à esquerda fosse um uint8_t, ele poderia ser promovido a int antes do deslocamento no Uno R3. Quando a intenção exigir uma largura sem sinal específica, converta o operando antes de deslocar; converter somente o resultado não muda a operação que já ocorreu.

Depois de validar 0 a 7, o resultado de 1U << posicao está entre 0x0001 e 0x0080, portanto cabe em uint8_t.

Validação seguida da construção

Bloco direto para criar uma máscara de byte. A variável começa com zero caso a posição seja recusada.

cpp
#include <stdint.h>

int posicao = 5;
uint8_t mascara = 0U;

if (posicao >= 0 && posicao <= 7) {
  unsigned int resultado = 1U << posicao;
  mascara = static_cast<uint8_t>(resultado);
}

// Para posicao = 5, mascara vale 0x20.

Atenção

Não use a conversão como correção

Uma conversão posterior para uint8_t não corrige uma operação inválida. Por exemplo, 1U << 16 é inválido no Uno R3 porque 16 não é menor que a largura de 16 bits de unsigned int. Também não execute um deslocamento com contagem negativa.

A regra posicao <= 7 é ainda mais restritiva: ela protege a finalidade de criar uma máscara de byte. Ela rejeita a posição 8 mesmo que 1U << 8 seja válido em 16 bits.

Complete a construção segura

Prática: máscara de um bit

Complete a recusa das posições que não pertencem a um byte e o literal usado para iniciar a máscara.

cpp
#include <stdint.h>

int posicao = 7;
uint8_t mascara = 0U;

if (posicao  || posicao ) {
  // Posição recusada: mascara continua em 0.
} else {
  unsigned int resultado =  << posicao;
  mascara = static_cast<uint8_t>(resultado);
}

Verifique sua decisão

Confira os limites

No Uno R3, classifique as posições para a expressão 1U << posicao quando o objetivo final é produzir uma máscara de uint8_t. Qual alternativa está correta?

Passo 7 de 9

Leia um campo de bits

Isole um campo contíguo de um byte, alinhe-o ao bit zero e preserve o valor original.

Isole um campo contíguo

Campo: selecione e alinhe

Um campo de bits é um grupo de posições contíguas que guarda um pequeno valor dentro de uma variável. Para lê-lo, use duas etapas sem alterar o byte original:

  1. Aplique uma máscara para manter somente o campo.
  2. Desloque o resultado à direita até o bit menos significativo.

Considere status = 0xB4, ou 10110100 em oito bits. Vamos interpretar as posições 5, 4 e 3 como um campo de três bits. A máscara 0x38U seleciona exatamente essas posições.

Depois de isolar o campo, seu padrão ainda está nas posições 5 a 3. O deslocamento de três posições o alinha à posição 0, permitindo lê-lo como um número de 0 a 7.

Do campo ao valor

Diagrama de um byte com três posições contíguas destacadas e, abaixo, o mesmo padrão deslocado para as três posições menos significativas.

A máscara isola o campo; o deslocamento à direita transforma sua posição em valor numérico.

Leia sem modificar o byte

Tipo efetivo e faixa

No Uno R3, um uint8_t pode ser promovido para int em uma expressão. Para deixar explícito que o isolamento e o deslocamento devem usar um tipo sem sinal, converta o valor para uint16_t antes dessas operações.

A máscara produz 0x30: os bits externos ao campo se tornam zero. Em seguida, 0x30 >> 3 resulta em 0x06, isto é, o valor decimal 6.

O campo tem apenas três bits, portanto seu resultado está entre 0 e 7. Só então é seguro representá-lo em uint8_t. Note que status não recebe nenhuma atribuição: ele continua sendo 0xB4.

Extraia o campo das posições 5 a 3

Exemplo de extração das posições 5, 4 e 3 de um byte.

cpp
#include <stdint.h>

const uint8_t status = 0xB4U;  // 10110100

const uint16_t isolado =
    static_cast<uint16_t>(status) & 0x38U;

const uint8_t modo =
    static_cast<uint8_t>(isolado >> 3);

// status = 0xB4; isolado = 0x30; modo = 6

Confira seu rastreamento

Pratique a extração

O diagrama mostra um campo nas posições 5, 4 e 3 do byte 0xB4. Complete o código para isolá-lo, alinhá-lo ao bit 0 e registrar o valor previsto. Não há atribuição a status, portanto o byte de origem deve permanecer preservado.

Máscara, deslocamento e resultado

Preencha a máscara, a quantidade de deslocamento e o valor extraído.

cpp
#include <stdint.h>

const uint8_t status = 0xB4U;
const uint16_t isolado =
    static_cast<uint16_t>(status) & ;
const uint8_t modo =
    static_cast<uint8_t>(isolado >> );
const uint8_t previsto = ;

Passo 8 de 9

Combine e recupere dois bytes

Componha uma palavra de 16 bits a partir de dois bytes e recupere exatamente os valores originais.

Visualize a composição

Dois bytes em uma palavra de 16 bits

Uma palavra uint16_t tem 16 bits. Para reunir dois uint8_t, trate um como byte mais significativo (alto) e o outro como byte menos significativo (baixo):

  • alto ocupará as posições 15 a 8.
  • baixo ocupará as posições 7 a 0.

Exemplo: alto = 0xA5 e baixo = 0x3C formam a palavra 0xA53C.

Isso descreve o valor da variável. Não informa nem depende de como bytes podem estar dispostos na memória física.

Visualize as duas regiões

Diagrama de uma palavra de 16 bits formada pelo byte alto 0xA5 à esquerda e pelo byte baixo 0x3C à direita, resultando em 0xA53C.

O byte alto ocupa os oito bits superiores; o byte baixo ocupa os oito bits inferiores.

Aplique a sequência segura

Use intermediários de 16 bits

No Uno R3, converta alto para uint16_t antes de deslocá-lo oito posições. Assim, o deslocamento atua sobre um operando sem sinal de 16 bits, em vez de depender da promoção de uint8_t para int.

Depois, use OR para unir as regiões que não se sobrepõem. Também convertemos baixo para uint16_t, deixando os dois operandos do OR na mesma largura explícita.

Para recuperar os valores, mantenha somente os oito bits inferiores ou desloque os oito superiores para a direita. A máscara 0x00FFU confirma que o resultado está entre 0x00 e 0xFF; só então ele é convertido para uint8_t. Nenhuma dessas operações altera palavra.

Código completo

Composição e recuperação de dois bytes

cpp
#include <stdint.h>

uint8_t alto = 0xA5;
uint8_t baixo = 0x3C;

uint16_t palavra =
    (static_cast<uint16_t>(alto) << 8) |
    static_cast<uint16_t>(baixo);

uint8_t baixoRecuperado =
    static_cast<uint8_t>(palavra & 0x00FFU);
uint8_t altoRecuperado =
    static_cast<uint8_t>((palavra >> 8) & 0x00FFU);

Rastreamento hexadecimal

Confira a ida e a volta em hexadecimal. Os casos 0x80 e 0xFF no byte alto confirmam que o bit 7 desse byte é preservado quando passa à posição 15 da palavra.

Em cada linha, os bytes recuperados devem ser iguais aos bytes de origem.
altobaixopalavraalto recuperadobaixo recuperado
0x000x000x00000x000x00
0xA50x3C0xA53C0xA50x3C
0x800x010x80010x800x01
0xFF0x7E0xFF7E0xFF0x7E

Pratique a composição

Qual é a ordem necessária?

Ordene as ações indispensáveis para compor uma palavra a partir de alto e baixo. A última ação já inclui a conversão de baixo.

  1. Converter `alto` para `uint16_t`.
  2. Unir o resultado a `static_cast<uint16_t>(baixo)` com OR e armazenar a palavra.
  3. Deslocar o valor convertido de `alto` oito posições à esquerda.

Confirme o caminho de volta

Complete a composição e a extração

Complete a composição e a extração para formar 0x8001 e recuperar 0x80 e 0x01. A primeira lacuna deve ser uma conversão que resulte em código válido antes de (alto).

cpp
uint8_t alto = 0x80;
uint8_t baixo = 0x01;

uint16_t palavra = ((alto) << 8) 
    static_cast<uint16_t>(baixo);

uint8_t baixoRecuperado =
    static_cast<uint8_t>(palavra & 0x00FFU);
uint8_t altoRecuperado =
    static_cast<uint8_t>((palavra  8) & 0x00FFU);

Confira o byte alto com bit 7 ativo

Com alto = 0xFF e baixo = 0x7E, qual deve ser o valor de palavra em hexadecimal?

Passo 9 de 9

Aplicação final: confira as máscaras e os resultados

Resolva uma sequência integrada de manipulação de bits e confira limites, tipos, preservação de flags e recuperação de bytes.

Planeje a verificação

Lista de conferência

Nesta aplicação final, você vai prever e conferir uma sequência completa usando apenas variáveis comuns.

Antes de aceitar um resultado, percorra esta lista:

  1. A posição destinada a uma máscara de byte está entre 0 e 7?
  2. O operando à esquerda do deslocamento tem o tipo pretendido, como 1U?
  3. A contagem é menor que a largura efetiva desse operando?
  4. O resultado foi limitado à faixa do destino antes da conversão para uint8_t, quando necessário?
  5. Os bits fora da máscara permaneceram iguais?

Uma conversão posterior não torna válido um deslocamento que já era inválido.

Rastreamento da aplicação

Diagrama mostrando um byte atualizado pela máscara, um campo extraído e dois bytes unidos em uma palavra de 16 bits e recuperados.

A máscara altera ou seleciona apenas as posições previstas; a conferência compara os bits externos antes e depois.

Dica

Previsão em hexadecimal

Faça a previsão antes de executar o código:

  • Com inicial = 0x25 e a máscara do bit 7, a ativação produz 0xA5.
  • Os sete bits inferiores continuam iguais: 0xA5 & 0x7F resulta em 0x25.
  • No valor 0xA5, o campo selecionado por 0x38, deslocado três posições à direita, vale 0x04.
  • Uma posição igual a 8 deve ser recusada para uma máscara de byte. Embora 1U << 8 seja válido para unsigned int de 16 bits no Uno R3, ele não representa uma máscara que caiba em uint8_t.

Execute ou inspecione o sketch

Sketch de conferência local opcional

Copie este sketch para a IDE Arduino apenas se quiser fazer a conferência local. Ele usa somente o LED integrado: após a gravação, o LED fica aceso quando todas as comparações são verdadeiras.

cpp
#include <stdint.h>

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  const uint8_t inicial = 0x25;
  const uint8_t posicao = 7;
  const uint8_t posicaoInvalida = 8;

  bool posicaoValida = (posicao < 8U);
  bool rejeitouInvalida = (posicaoInvalida >= 8U);

  uint8_t flags = inicial;
  uint8_t mascara = 0U;
  if (posicaoValida) {
    unsigned int resultado = 1U << posicao;
    mascara = static_cast<uint8_t>(resultado);
    flags = static_cast<uint8_t>(
        static_cast<unsigned int>(flags) |
        static_cast<unsigned int>(mascara));
  }

  bool flagsCorretas = (flags == 0xA5U);
  bool externosPreservados =
      ((flags & 0x7FU) == (inicial & 0x7FU));

  uint8_t campo = static_cast<uint8_t>(
      (static_cast<uint16_t>(flags) & 0x0038U) >> 3);
  bool campoCorreto = (campo == 0x04U);

  const uint8_t alto = 0xA5;
  const uint8_t baixo = 0x3C;
  uint16_t palavra =
      (static_cast<uint16_t>(alto) << 8) |
      static_cast<uint16_t>(baixo);

  uint8_t altoRecuperado = static_cast<uint8_t>(
      (palavra >> 8) & 0x00FFU);
  uint8_t baixoRecuperado = static_cast<uint8_t>(
      palavra & 0x00FFU);
  bool bytesRecuperados =
      (altoRecuperado == alto) && (baixoRecuperado == baixo);

  bool tudoCerto = posicaoValida && rejeitouInvalida &&
                    flagsCorretas && externosPreservados &&
                    campoCorreto && bytesRecuperados;
  digitalWrite(LED_BUILTIN, tudoCerto ? HIGH : LOW);
}

void loop() {
}
Saída esperada
Após compilar e gravar sem alterações no Arduino Uno R3:
LED integrado: aceso continuamente

O sketch não envia texto ao computador. A saída observável é o estado do LED integrado: aceso indica que todas as comparações programadas passaram; apagado indica que alguma comparação falhou ou que o código foi alterado.

Interprete a conferência

O que conferir

No Uno R3, o byte alto é convertido para uint16_t antes de << 8. Portanto, o deslocamento ocorre sobre um operando sem sinal de 16 bits, e deslocar oito posições é válido.

O sketch também não constrói máscara para posicaoInvalida = 8: ele apenas confirma que esse valor deve ser rejeitado para a finalidade de um byte. Se você executar a prática, LED aceso confirma as comparações programadas; LED apagado indica que alguma expectativa não foi satisfeita.

Aplique o limite antes do deslocamento

Construa a máscara com segurança

Complete a função para recusar posições que não pertencem a um byte antes de construir a máscara.

cpp
uint8_t mascaraDeBit(uint8_t posicao) {
  if (posicao ) {
    return 0U;
  }

  return static_cast<uint8_t>( << posicao);
}

Confira e encerre

Resultados esperados

Use esta tabela como rastreamento da sequência. A composição usa alto = 0xA5 e baixo = 0x3C.

Valores previstos da aplicação integrada, em hexadecimal.
EtapaExpressão ou valorResultado
Máscara do bit 71U << 70x0080
Ativação0x25 | 0x800xA5
Bits externos0xA5 & 0x7F0x25
Campo(0xA5 & 0x38) >> 30x04
Composição0xA5 << 8 | 0x3C0xA53C
Byte alto(0xA53C >> 8) & 0x00FF0xA5
Byte baixo0xA53C & 0x00FF0x3C

Justifique a segurança

Justifique em poucas frases: por que a posição 8 é rejeitada para uma máscara de byte e por que alto é convertido para uint16_t antes de << 8? Inclua os dois bytes recuperados da palavra 0xA53C.

Escreva pelo menos 80 caracteres (0/80).

Resumo

Síntese final

  • Para construir uma máscara de byte, valide a posição de 0 a 7 antes do deslocamento.
  • Comece uma máscara de um bit com 1U e confira a largura efetiva do operando.
  • Use máscaras e deslocamentos para consultar flags e extrair campos sem modificar o valor de origem.
  • Converta o byte alto para uint16_t antes de deslocá-lo por oito posições.
  • Rastreie em hexadecimal, confira os bits externos preservados e valide os bytes recuperados.

Tutorial concluído

Parabéns! Você concluiu: Máscaras e operações de bits em C/C++

Excelente! Agora você pode validar deslocamentos, preservar bits externos e confirmar resultados em hexadecimal antes de confiar no firmware.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso