Diferenças
Aqui você vê as diferenças entre duas revisões dessa página.
c:operacoes_com_bits [2023/08/01 18:12] – criada maziero | c:operacoes_com_bits [2023/08/01 20:16] (atual) – edição externa 127.0.0.1 | ||
---|---|---|---|
Linha 1: | Linha 1: | ||
+ | ====== Operações com bits ====== | ||
+ | |||
+ | {{ progc_bits.mkv |Video desta aula}} | ||
+ | |||
+ | {{ bits.png? | ||
+ | |||
+ | ===== Bit fields ===== | ||
+ | |||
+ | Ao construir programas que manipulem muitos dados, é importante escolher o tipo mais adequado para cada variável. Por exemplo, podemos definir uma estrutura de dados para armazenar datas/horas da seguinte forma: | ||
+ | |||
+ | <code c> | ||
+ | typedef struct | ||
+ | { | ||
+ | unsigned int year, month, day, hour, min, sec ; | ||
+ | } date_t ; | ||
+ | </ | ||
+ | |||
+ | Usando essa estrutura, cada variável do tipo '' | ||
+ | |||
+ | No entanto, usar '' | ||
+ | |||
+ | <code c> | ||
+ | typedef struct | ||
+ | { | ||
+ | unsigned short year ; | ||
+ | unsigned char month, day ; | ||
+ | unsigned char hour, min, sec ; | ||
+ | } date_t ; | ||
+ | </ | ||
+ | |||
+ | Em máquinas com arquitetura de 32 ou 64 bits, cada variável to tipo '' | ||
+ | |||
+ | * 5 bytes para os 5 '' | ||
+ | * 2 bytes para o '' | ||
+ | * 1 byte de //padding// (" | ||
+ | |||
+ | O //padding// é necessário porque as variáveis inteiras deve estar " | ||
+ | |||
+ | Entretanto, há espaço para economizar mais memória, pois os campos da estrutura não precisam usar toda a faixa de valores oferecida por seus tipos: | ||
+ | |||
+ | ^ campo ^ faixa ^ bits necessários ^ | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | TOTAL | | 38 | | ||
+ | |||
+ | A linguagem C permite definir variáveis inteiras com um número específico de bits **dentro de structs**, através de uma funcionalidade chamada // | ||
+ | |||
+ | <code c> | ||
+ | typedef struct | ||
+ | { | ||
+ | unsigned short year:12 ; | ||
+ | unsigned char month:4 ; | ||
+ | unsigned char day:5 ; | ||
+ | unsigned char hour:5 ; | ||
+ | unsigned char min:6 ; | ||
+ | unsigned char sec:6 ; | ||
+ | } date_t ; | ||
+ | </ | ||
+ | |||
+ | Essa nova estrutura demanda 38 bits, ou 4,75 bytes. Devido à necessidade de usar bytes inteiros e do alinhamento, | ||
+ | |||
+ | O código abaixo apresenta os tamanhos das três estruturas: | ||
+ | |||
+ | <code c struct-size.c> | ||
+ | #include < | ||
+ | |||
+ | typedef struct | ||
+ | { | ||
+ | unsigned int year, month, day, hour, min, sec ; | ||
+ | } date_t1 ; | ||
+ | |||
+ | typedef struct | ||
+ | { | ||
+ | unsigned short year ; | ||
+ | unsigned char month, day ; | ||
+ | unsigned char hour, min, sec ; | ||
+ | } date_t2 ; | ||
+ | |||
+ | typedef struct | ||
+ | { | ||
+ | unsigned short year:12 ; | ||
+ | unsigned char month:4 ; | ||
+ | unsigned char day:5 ; | ||
+ | unsigned char hour:5 ; | ||
+ | unsigned char min:6 ; | ||
+ | unsigned char sec:6 ; | ||
+ | } date_t3 ; | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | printf (" | ||
+ | printf (" | ||
+ | printf (" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | // | ||
+ | |||
+ | <code c> | ||
+ | struct DISK_REGISTER | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } ; | ||
+ | </ | ||
+ | |||
+ | Outro exemplo muito interessante de uso de // | ||
+ | |||
+ | Formato do '' | ||
+ | |||
+ | * sinal (1 bit) | ||
+ | * expoente (8 bits) | ||
+ | * mantissa (23 bits) | ||
+ | |||
+ | <code c> | ||
+ | // extraído (e simplificado) de / | ||
+ | |||
+ | union ieee754_float | ||
+ | { | ||
+ | float f; | ||
+ | |||
+ | /* This is the IEEE 754 single-precision format. | ||
+ | struct | ||
+ | { | ||
+ | #if __BYTE_ORDER == __BIG_ENDIAN | ||
+ | unsigned int negative:1; | ||
+ | unsigned int exponent:8; | ||
+ | unsigned int mantissa: | ||
+ | # | ||
+ | #if __BYTE_ORDER == __LITTLE_ENDIAN | ||
+ | unsigned int mantissa: | ||
+ | unsigned int exponent:8; | ||
+ | unsigned int negative:1; | ||
+ | # | ||
+ | } ieee; | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | Cuidados a tomar no uso de // | ||
+ | |||
+ | * elementos de // | ||
+ | * muitos compiladores limitam o tamanho de um bitfield ao tamanho máximo de um inteiro (16, 32 ou 64 bits); | ||
+ | * vetores de // | ||
+ | * o uso de // | ||
+ | |||
+ | ===== Acesso a bits individuais ===== | ||
+ | |||
+ | Para testar um bit específico em uma variável inteira, podemos efetuar um AND bit a bit entre essa variável e uma máscara de bits (// | ||
+ | |||
+ | Por exemplo: para verificar se o 4° bit de '' | ||
+ | |||
+ | <code c> | ||
+ | if ( value & 0x8 ) // 0x8 = 0000 1000 em binário | ||
+ | { | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Para ativar um bit específico, | ||
+ | |||
+ | Por exemplo: para ativar o 3° bit de '' | ||
+ | |||
+ | <code c> | ||
+ | value = value | 0x4 ; // 0x4 = 0000 0100 em binário | ||
+ | value |= 0x4 ; // versão compacta | ||
+ | </ | ||
+ | |||
+ | Similarmente, | ||
+ | |||
+ | < | ||
+ | value = value & ~ 0x4 ; // explique! | ||
+ | </ | ||
+ | |||
+ | A máscara de bits também pode ser gerada por deslocamentos: | ||
+ | |||
+ | ^ bit ^ máscara | ||
+ | | 0 | 0000 0001 | '' | ||
+ | | 1 | 0000 0010 | '' | ||
+ | | 2 | 0000 0100 | '' | ||
+ | | 3 | 0000 1000 | '' | ||
+ | | 4 | 0001 0000 | '' | ||
+ | | 5 | 0010 0000 | '' | ||
+ | | 6 | 0100 0000 | '' | ||
+ | | 7 | 1000 0000 | '' | ||
+ | |||
+ | <code c> | ||
+ | if ( value & (1 << 3) ) // testa o 4° bit de value | ||
+ | { | ||
+ | value = value | 1 << 2 ; // ativa o 3° bit de value | ||
+ | value = value & ~ 1 << 5 ; // desliga o 6° bit de value | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | O exemplo a seguir imprime os valores binários dos números de 0 a 255. Observe o teste para verificar se um determinado bit é 0 ou 1: | ||
+ | |||
+ | <code c write-bits.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | short n, b ; | ||
+ | |||
+ | // para os 256 primeiros inteiros | ||
+ | for (n = 0; n < 256; n++) | ||
+ | { | ||
+ | printf ("%3d: ", n) ; | ||
+ | |||
+ | // testa os 8 bits menos significativos | ||
+ | for (b = 7; b >= 0 ; b--) | ||
+ | // testa e imprime o b-ésimo bit | ||
+ | n & ( 1<<b ) ? putchar (' | ||
+ | |||
+ | putchar (' | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Bitmaps ===== | ||
+ | |||
+ | Bits individuais na memória podem ser utilizados para representar valores true/false com muita economia de memória. Um //bitmap// é um vetor de bits, onde cada bit individual pode ser lido ou ajustado como 0 ou 1: | ||
+ | |||
+ | 000100101111011010101100100001011001010101011111010100010101011... | ||
+ | |||
+ | Estruturas de bitmaps são muito úteis para representar conjuntos. Por exemplo, em um //bitmap// com 1000 bits, um bit em 1 na posição 375 indica que o elemento 375 pertence ao conjunto. | ||
+ | |||
+ | Bitmaps não são diretamente suportados pela linguagem C, mas são simples de construir. Bitmaps de pequeno tamanho podem ser implementados diretamente sobre variáveis inteiras, usando os operadores de bits vistos acima. Para bitmaps maiores, podem ser implementadas funções ou macros que operem sobre vetores de inteiros de tamanho adequado. | ||
+ | |||
+ | A implementação a seguir, extraída da [[http:// | ||
+ | |||
+ | <code c> | ||
+ | #include < | ||
+ | |||
+ | // auxiliar macros | ||
+ | #define BITMASK(b) | ||
+ | #define BITSLOT(b) | ||
+ | #define BITNSLOTS(nb) | ||
+ | |||
+ | // bit operations | ||
+ | #define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b)) | ||
+ | #define BITCLEAR(a, b) ((a)[BITSLOT(b)] &= ~BITMASK(b)) | ||
+ | #define BITTEST(a, b) | ||
+ | #define BITTOGGLE(a, | ||
+ | </ | ||
+ | |||
+ | Exemplos de uso dessa implementação: | ||
+ | |||
+ | <code c> | ||
+ | // declara um mapa de 12000 bits | ||
+ | char bitarray[BITNSLOTS (12000)] ; | ||
+ | |||
+ | // ativa o bit 23 | ||
+ | BITSET (bitarray, 23) ; | ||
+ | |||
+ | // testa o bit 3451 | ||
+ | if (BITTEST (bitarray, 3451)) | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Exercícios ===== | ||
+ | |||
+ | (sugeridos pelo monitor Eric Low Schmidt) | ||
+ | |||
+ | - Crie uma estrutura que seja capaz de analisar uma letra e, através de // | ||
+ | - Um valor correspondente a letra ( Ex.: a=0, b=1, c=2... ). | ||
+ | - Se a letra é vogal. | ||
+ | - Se a letra é maiúscula. | ||
+ | - Se a letra vem depois da letra M. | ||
+ | - Se a letra é a última ou a primeira do alfabeto. | ||
+ | - Se a letra possui uma letra vizinha que é vogal. | ||
+ | - O número de letras até a última letra do alfabeto. | ||
+ | - Sem utilizar a operação módulo (%), crie uma função capaz de calcular o módulo de um número por 2, por 4 ou por 16. | ||
+ | - Sem utilizar a operação de maior (>), crie uma função capaz de determinar se um número é maior que 7, 31 ou 63. | ||
+ | - Sem utilizar a operação de divisão, determine o resultado da operação de divisão de um número por 16 e o resto dessa divisão. | ||
+ | - Crie uma função capaz de dizer quantos bits " | ||
+ | - É possível descobrir se um número é uma potência de dois se este número possui apenas um bit 1 em sua representação binária. Crie uma função que indica quando um número é uma potência de dois. | ||