Skip to content

Hexadecimal: A Linguagem Secreta dos Programadores

O capítulo está quase se encerrando. Ao longo dele você aprendeu como computadores representam números, aprendeu como representam letras, aprendeu como representam idiomas inteiros. Você aprendeu algo que a maioria das pessoas sempre se perguntaram internamente e apenas aceitaram como se fosse mágica. Você sabe que por baixo de tudo, de cada letra, de cada emoji que você manda numa mensagem, de cada página de qualquer site que você visita, existem apenas zeros e uns. Bits agrupados em bytes, bytes formando números, números mapeados para símbolos por tabelas como o ASCII e o Unicode, respeitando padrões estabelecidos, como o UTF-8.

Até agora, você aprendeu como o computador representa a informação fisicamente, mas ainda não conversamos sobre o que é, de verdade, trabalhar com essa informação do outro lado - não usando o computador, mas construindo os softwares (códigos / programas) que rodam nele.

E é com essa perspectiva que você verá a necessidade de ainda uma outra forma de representar as coisas: o hexadecimal.

O que um programador realmente faz

Existe uma imagem popular do que é ser programador: uma pessoa digitando código numa tela escura, e as coisas simplesmente funcionando. E sabe o que é engraçado? Essa imagem não está errada! Não está errada, mas está longe de ser completa.

Uma parte enorme (eu diria que é a maior parte) do trabalho de um programador não é escrever código novo. É entender por que o código que já existe está se comportando da maneira que está se comportando. Irónico, não é? Um código não é feito por uma pessoa só e sim por várias. É preciso entender qual era a intenção do outro programador quando escreveu algum trecho de código que você está responsável por melhorar agora, ou até entender o seu próprio código de 6 meses atrás que você nem se lembra mais por que escreveu de tal jeito. Um cálculo que deveria dar 100 está dando 99. Um texto que deveria aparecer com acento está chegando corrompido. Um programa que deve terminar em segundos fica rodando por horas.

Esses problemas são inevitáveis. Todo programador os encontra, independentemente do nível de experiência ou do cuidado com que escreve o código. Encontrá-los e corrigi-los é uma habilidade tão fundamental quanto escrever o código em si. E esse processo tem um nome que você já ouviu pelo menos uma vez na vida: depuração, do inglês debugging.

Por que isso acontece?

Grande parte do tempo gasto tentando entender código não acontece apenas porque o código pode estar errado ou ter alguma falha em algum lugar, acontece porque, ao longo dos anos, muitos programadores se acostumaram a escrever código pensando apenas em fazê-lo funcionar e não necessariamente em escrevê-lo de forma fácil para ler e compreender depois.

Enquanto o código está fresco na cabeça de quem o escreveu, tudo parece claro. Mas dias, semanas ou meses depois, até o próprio autor pode ter dificuldade para entender o que estava pensando na epóca. E quando outra pessoa precisa trabalhar naquele código, a dificuldade pode ser ainda maior.

Isso é comum e acontece com todos. Grande parte das vezes você estará entusiasmado escrevendo código e não se preocupará com compreensibilidade logo de cara. Afinal, está compreensível para você. O erro é achar que também estará daqui a uns dias, ou que está para os outros.

No Ultimate Rust, você aprenderá não apenas a escrever código que funciona, mas também código claro, legível e compreensível, que outras pessoas - e o seu próprio "eu do futuro" - conseguem entender sem sofrimento.

A código com essas características, damos o nome de código idiomático. Nós voltaremos a esse assunto mais tarde.

Ao processo de ficar tentando entender porque o código está se comportando da maneira que está, dá-se o nome de depuração, do inglês debugging. A história por trás dessa palavra é curiosa.

Debugging: o que é um bug?

Em 1947, uma das primeiras programadoras da história, Grace Hopper, enquanto trabalhava na Universidade de Harvard a serviço da Marinha dos EUA, percebeu que o computador com que trabalhava estava com mal funcionamento, e descobriu a falha: um inseto real que havia entrado no hardware e interrompido o circuito. O inseto era uma mariposa. Ela colou a mariposa no diário de bordo com uma anotação "first actual case of a bug being found", que traduzido significa "primeiro caso real de um bug (inseto em inglês) sendo encontrado". O termo bug para problema num programa nasceu ali, e se popularizou de tal forma que debugging (remover o bug) virou sinônimo de investigar e corrigir falhas.

Primeiro bug de computador registrado, colado no diário de bordo da equipe de Grace Hopper em 9 de setembro de 1947
O primeiro bug de computador registrado, 1947 Cortesia do Naval Surface Warfare Center, Dahlgren, VA., 1988. Domínio público, via Wikimedia Commons.

INFO

O diário de bordo com a mariposa colada existe até hoje e está exposto no Museu Nacional Americano de História. É um dos itens mais famosos da história da computação.

Depurar significa investigar por dentro

Depurar significa investigar. E investigar, por sua vez, em algum momento pode significar precisar olhar para dentro do computador. Eu não me refiro ao sentido literal de olhar para o computador físico - ou às vezes sim, como fez a nossa amiga Grace Hopper - e sim mais dentro ainda: dentro da memória. Se os programas do seu notebook, aplicativos do celular, etc... rodam na memória e você quer investigar o que está acontecendo passo a passo, você também vai querer saber quais valores estão sendo guardados na memória para ver se são os esperados, o que o programa está calculando com eles... Tudo isso para descobrir onde exatamente as coisas saíram do trilho e descobrir que parte do seu código causou o problema.

Mas veja bem o que isso significa. Você se lembra de como os dados estão guardados na memória? Exatamente! Em zeros e uns. Quando um programador precisa inspecionar esses dados para entender o que está acontecendo, ele está olhando para sequências de zeros e uns.

01000010 01000001 01011010 01001001 01001110 01000111 01000001 00100001

Hoje existem ferramentas que tornam esse processo mais fácil e dificilmente você precisará olhar para zeros e uns literalmente. Elas convertem os valores para formas legíveis, que permitem pausar um programa e inspecionar cada variável. Mas essas ferramentas são uma conquista relativamente recente. Por décadas, não existiam e os programadores tinham que literalmente ficar vendo 0s e 1s aos montes.

Quando programadores liam folhas de papel

Nos anos 1950 e 1960, depurar um programa era um trabalho chato, manual e físico que hoje seria difícil de imaginar.

Praticamente não havia telas. Os computadores da época eram máquinas enormes que ocupavam salas inteiras, e "se comunicavam" com os programadores principalmente por meio de teletipos e impressoras, que imprimiam os resultados do programa em papel. Um exemplo perfeito para isso, é o próprio Harvard Mark II, o tal computador que a Grace Hopper e sua equipe utilizavam quando encontraram a mariposa. Como a maioria das máquinas da sua época, ele recebia programas por fitas perfuradas e produzia resultados impressos em papel, sem nenhuma tela.

Harvard Mark I, predecessor do Harvard Mark II
Harvard Mark I (1944) Jerry Stratton, via Wikimedia Commons
Harvard Mark III, sucessor do Harvard Mark II
Harvard Mark III (1949)U.S. Navy, domínio público, via Wikimedia Commons

Nota

Em minhas pesquisas, foi bastante difícil encontrar fotografias dessas máquinas com licença livre. Para o Harvard Mark II não encontrei nenhuma, por isso recorri ao seu predecessor (Mark I, 1944) e ao seu sucessor (Mark III, 1949). O Mark III também não ficou de fora dessa dificuldade: a única versão que encontrei com licença livre é esta, de qualidade bastante limitada.

Quando um programa dava errado nessas máquinas, a forma de investigar era solicitar ao computador algo chamado memory dump - em português, "despejo de memória". Não é um nome muito bonito, mas é bastante descritivo por si só: o computador simplesmente "despeja" tudo que estava na memória para uma folha de papel. Despejar aqui obviamente significa imprimir. Mas não se engane, não era um papel bonitinho com uma visualização organizada com variáveis e valores. Não era uma lista comentada explicando o que cada coisa significa e onde provavelmente estava o erro. Era o conteúdo bruto da memória, byte por byte, do jeito que o computador os armazena.

E como você já sabe muito bem, o computador armazena tudo de uma forma só.

Em zeros e uns.

Imagina acordar de manhã, tomar um café, chegar no trabalho, e receber isso para analisar:

01011001 01101111 01110101 00100000 01100001 01110010 01100101 00100000 01101100 01100101 01100001 01110010 01101110 01101001 01101110 01100111
00100000 01010010 01110101 01110011 01110100 00100000 01110111 01101001 01110100 01101000 00100000 01110100 01101000 01100101 00100000 01100010
01100101 01110011 01110100 00100000 01110010 01100101 01110011 01101111 01110101 01110010 01100011 01100101 00100000 01100101 01110110 01100101
01110010 00111010 00100000 01010101 01101100 01110100 01101001 01101101 01100001 01110100 01100101 00100000 01010010 01110101 01110011 01110100
00100000 00101000 01101000 01110100 01110100 01110000 01110011 00111010 00101111 00101111 01110101 01110010 01110101 01110011 01110100 00101110
01101111 01110010 01100111 00101001 00101110 00100000 01000010 01100101 01101100 01101111 01110111 00100000 01101001 01110011 00100000 01100001
01101110 00100000 01100101 01111000 01100001 01101101 01110000 01101100 01100101 00100000 01101111 01100110 00100000 01100001 00100000 01100011
01101111 01110010 01110010 01110101 01110000 01110100 01100101 01100100 00100000 01110100 01100101 01111000 01110100 00100000 01110100 01101000
01100001 01110100 00100000 01110000 01110010 01101111 01100111 01110010 01100001 01101101 01101101 01100101 01110010 01110011 00100000 01110011
01101111 01101101 01100101 01110100 01101001 01101101 01100101 01110011 00100000 01101000 01100001 01110110 01100101 00100000 01110100 01101111
00100000 01100100 01100101 01100010 01110101 01100111 00111010 00100000 11101111 10111111 10111101 11101111 10111111 10111101

Esse não é um exemplo inventado para te assustar. Essa era a realidade cotidiana das pessoas que estavam construindo, do zero, o software que tornaria possível tudo que você usa hoje. Elas recebiam folhas assim (às vezes com dezenas de linhas, outras com centenas) e precisavam encontrar o erro escondido em algum lugar no meio disso tudo.

Tente ficar alguns segundos acompanhando os números sem se perder.

Difícil, não é? Agora imagina fazer isso como trabalho, várias vezes por dia.

Precisava de algo melhor. Mas o quê?

A solução óbvia que não funcionou: decimal

Você acabou de receber o memory dump impresso pelo computador. Seu programa falhou, você precisa entender o que aconteceu, e fica encarando aquele papel cheio de zeros e uns se perguntando por onde começar.

Se eu lhe conheço, meu caro leitor, a primeira coisa que provavelmente passou pela sua cabeça é:

Tudo bem, eu sei converter qualquer binário para decimal. Aprendi na seção 1.4. É trabalhoso, mas é possível. Vou converter tudo e aí consigo ler os valores.

É um pensamento completamente razoável, de fato você conseguiria. Vamos seguir ele até o fim e ver onde ele leva.

Fazendo a conta de verdade

Vamos pegar o primeiro byte daquele dump: 01011001. Você já sabe o processo: cada bit vale uma potência de 2 dependendo da sua posição:

Convertendo o binário 01011001 para decimalConvertendo binário 01011001 para decimal
Convertendo o binário 01011001 para decimal Richard Dias Alves @ Ultimate Rust, 2025. Licença CC BY-NC-SA 4.0.

Resultado: 89. Agora, cruzando com a tabela ASCII da seção 1.5, vemos que 89 equivale a Y. Funcionou! Agora é só repetir isso para os outros 173 bytes do dump.


Pare para pensar um pouco. Pense no que acabamos de fazer para converter um único byte.

Nós fizemos 8 multiplicações e 8 somas. Para um byte. 174 bytes ao todo, então multiplicamos isso por 174. Seriam 174 vezes 8 multiplicações e 8 somas, totalizando 1392 multiplicações + 1392 somas - feitas à mão, num papel, para converter apenas esse trecho pequeno de um dump, que na realidade seria muito maior. Na prática, dumps reais podiam ter centenas ou milhares de bytes.

Mas sabe o que é pior do que a quantidade absurda de contas?

O perigo silencioso

Imagina que você está quase na metade do processo, já converteu 80 bytes diferentes, está cansado, e no byte 81 você comete um pequeno erro: confunde 2⁴ com 2⁵ numa multiplicação. Você vai chegar obiviamente a um número diferente do valor real (e boa sorte para perceber se foi erro do computador ou se foi seu, e se foi seu, aonde exatamente você errou).

E agora?

Agora você não sabe. Não tem nenhum sinal de alerta. Nenhuma indicação que algo deu errado. Você vai olhar para aquele número incorreto e acreditar que aquele é o valor real que estava na memória. Vai tomar decisões de depuração desnecessárias baseadas num dado que você mesmo inventou sem querer.

Num processo de depuração, um erro silencioso não é só ineficiente, é perigoso. Pode te fazer tentar corrigir um código que estava certo, ou ignorar o código que realmente estava errado. Você pode passar horas ou dias numa direção completamente equivocada e acabar criando mais problemas ainda antes de perceber que o problema era outro.

E mesmo que você não cometesse nenhum erro, mesmo que sua atenção fosse perfeita durante todas as mais de 1000+ operações, o processo todo seria absurdamente lento para uso cotidiano.

E novamente: se eu consegui construir seu fluxo de pensamento do jeito que eu esperava, agora você deve estar se perguntando:

"Mas e se o computador convertesse para mim?"

Faz todo sentido perguntar isso. Se o problema é que a conversão manual é lenta e propensa a erros, por que não programar o próprio computador para converter o dump para decimal antes de imprimir?

A verdade é que isso foi tentado. E não funcionou tão bem quanto parecia.

O motivo é que converter binário para decimal não era uma operação simples nem mesmo para o computador. Ela exige uma série de divisões sucessivas, que eram das operações mais custosas que um computador dos anos 1960 podia executar. A velocidade com que esses computadores operavam eram magnitudes menor da velocidade dos computadores de hoje, e converter um dump inteiro em decimal levaria um tempo tão grande que não compensava. Como o objetivo do dump era ser rápido - algo como uma fotografia imediata do estado da memória no momento da falha - desperdiçar tempo com conversão antes de imprimir era problemático.

Mas tem ainda outro problema e ainda mais profundo no decimal, que vai além de velocidade. Um problema estrutural que não desaparecere mesmo que a conversão seja instantânea.

O problema que a velocidade não resolve

Vamos dar um passo para trás e pensar em algo que parece óbvio mas que tem uma consequência muito importante: por que nós queremos ler em decimal?

Por pura conveniência. Literalmente. Crescemos aprendendo decimal, pensamos em decimal, compramos pão em decimal. Decimal é a nossa língua nativa para números.

O computador, porém, nunca ouviu falar de decimal. Ele não tem motivo histórico. físico ou matemático para ter qualquer relação especial com o número 10. Ele trabalha com dois estados apenas, e o único sistema numérico que tem uma relação natural e direta com isso é o binário.

Isso tem uma consequência direta que você sente na hora quando tenta trabalhar com um dump em decimal, mesmo que o dump inteiro já esteja convertido. O grande problema é: decimal não tem relação natural nenhuma com grupos de bits.

Por exemplo, pega o byte 11111111. Quantas multiplicações você teria que fazer para saber que esse é o valor 255? 8. E se eu te perguntar o contrário: qual é o byte do decimal 200 em binário? Você precisa fazer várias divisões sucessivas por 2 e guardando os restos, até chegar lá. Não existe nenhum atalho, nenhum padrão que você memoriza e aplica sem fazer contas. Toda conversão exige uma conta do início ao fim.

Como seria essa conversão?

Na seção sobre binários, você apenas aprendeu a converter números binários para decimal, mas não o inverso. Isso não foi acidental. Escolhi não ensinar esse processo inverso por fins didáticos. Não iria acrescentar em nada ao objetivo de construção mental necessária para você entender cada seção, e você aprendeu o suficiente para entender cada conceito.

Você não precisará converter números decimais para binário no dia a dia (e provavelmente o inverso também não), mas, para caso você tenha curiosidade, abaixo deixo uma imagem que mostra como seria isso.

onvertendo o decimal 200 para binárioonvertendo o decimal 200 para binário
Convertendo o decimal 200 para binário Richard Dias Alves @ Ultimate Rust, 2025. Licença CC BY-NC-SA 4.0.

200 em binário é 11001000.

Mesmo que o computador tenha feito toda conversão para você, depurar não é só ler valores. É pensar sobre eles. Quando você olha para 200 num dump em decimal, você consegue ler só o número, mas o que ele significa em bytes? Esse valor está próximo do limite que um byte consegue guardar? Ele caberia num único byte ou precisaria de dois? Você não sabe. Não tem como saber só de olhar. Precisaria acabar calculando.

Com binário, você olha para 11001000 e vê diretamente o que está na memória. Todo o objetivo aqui sempre foi esse: ler o que está na memória. Cada dígito é um bit, sem nenhuma camada entre você e o dado.

O problema do binário não é que ele esconde alguma coisa. É só que ele é longo demais para ser legível.

O que se precisava era de um sistema que mantivesse essa transparência do binário, onde olhar para um símbolo significasse ver diretamente os bits, mas que fosse compacto o suficiente para um ser humano conseguir ler. Decimal não é isso. Precisava de outra coisa.

A segunda ideia: inventar símbolos novos

Tudo bem. Decimal não funciona. Vamos tentar outra abordagem.

Se o problema é que nenhum sistema existente serve, que tal criar um do zero? Um sistema feito especificamente para representar bytes, sem nenhum compromisso com o que já existe. Você define as regras, define os símbolos, define tudo.

E a primeira decisão seria: quantos símbolos criar?

Um byte pode ter 256 valores diferentes, de 0 a 255. E se você criasse exatamente 256 símbolos, um para cada valor possível? Qualquer byte poderia ser representado por um único caractere. Um símbolo, um byte.

Funcionaria! E parece a solução ideal. Mas antes de comemorar, vamos parar por um segundo e pensar no que seria trabalhar com esse sistema no dia a dia.

Você teria 256 símbolos para aprender. Não são 26 letras do alfabeto que você foi assimilando ao longo de anos, com contexto e repetição em todos os cenários. Não são 10 dígitos que você usa desde criança para contar troco e marcar pontos num jogo. São 256 desenhos novos, criados do nada, cada um representando um valor diferente, sem nenhuma lógica conectado um ao outro. Sem história, sem contexto, sem nenhum padrão que ajude a memória além de decorar que x símbolo significa o byte 10101010.

Isso já seria um problema sério, mas tem algo pior.

Com 256 símbolos inventados sem relação entre eles, você não conseguiria raciocinar. Se eu colocasse dois desses símbolos na sua frente, excepto se você tivesse decorado todos os 256, você não teria como saber qual dos dois representa um valor maior sem consultar uma tabela. Mentalmente você não conseguiria comparar, somar, perceber se um valor já está próximo do limite de um byte. Os símbolos não teriam estrutura interna nenhuma. Seriam apenas desenhos.

E aí você percebe que esse sistema seria ainda pior do que o binário. O binário pelo menos tem uma lógica clara: 0 é zero, 1 é um, e você consegue raciocinar com eles depois de algum estudo. Com 256 símbolos inventados, você teria memorizado uma tabela enorme e ainda assim não conseguiria pensar com ela.

A ideia não foi a frente. Ficou claro que o caminho não era inventar algo do zero.

O que precisava existir era um sistema que tivesse relação natural e direta com os bits e bytes. Não algo novo. Algo já familiar, só que escolhido com muito mais cuidado do que o decimal foi.

A pergunta correta não era "que símbolos novos podemos criar?". Era "em qual base numérica os bits fazem sentido?"

O que realmente precisava existir?

Decimal não funciona. Inventar símbolos novos não funciona. O que exatamente precisaria existir para resolver o problema?

Quando você para para pensar com cuidado nos motivos pelos quais essas tentativas falharam, três requisitos aparecem normalmente. Não são requisitos que alguém sentou e listou numa reunião. São requisitos que a própria existência foi revelando conforme as soluções menos eficientes foram sendo tentadas e descartadas.

  1. Precisava ser compacto. Esse era o problema original. Para representar um byte em binário precisamos de 8 caracteres, e isso é longo demais para um ser humano conseguir ler com rapidez e confiança. O novo sistema precisava representar um byte com bem menos símbolos do que isso.
  2. Precisava ter lógica interna. Os 256 símbolos inventados falharam exatamente por não ter isso. O novo sistema precisava ter uma estrutura matemática, como decimal tem, que permite aprender, comparar valores e raciocinar sem precisar consultar uma tabela a cada passo.
  3. Precisava ter uma relação direta com os bits. Esse é o requisito mais importante, e é o que eliminou o decimal. A conversão entre o novo sistema e o binário precisava ser imediata, sem cálculos, sem multiplicações, sem divisões. Você olha para um símbolo e sabe na hora quais bits ele representa. Você olha para um grupo de bits e sabe na hora qual símbolo ele é.

É esse terceiro requisito que aponta o caminho para a solução. Pense no que "relação direta com os bits" significa. Se um símbolo do novo sistema sempre representa exatamente o mesmo grupo de bits, sem exceção, a substituição se torna pura substituição. Como trocar uma palavra por outra numa tradução onde cada palavra tem sempre um único equivalente e a correspondência nunca muda. Nenhuma conta. Só substituição.

Mas para isso funcionar, o número de símbolos do sistema precisa ser exatamente igual ao número de combinações possíveis de algum grupo de bits. E você já sabe como calcular isso:

  • Com 1 bit você tem 2 combinações.
  • Com 2 bits você tem 4 combinações.
  • Com 3 bits você tem 8.
  • Com 4 você tem 16.

O número de combinações é sempre uma potência de 2.

Isso significava que o sistema precisava ter exatamente 2, 4, 8, 16, 32... símbolos (alguma dessas alternativas). Qualquer outro número e a correspondência direta quebraria porque sempre faltaria ou sobraria combinações de bits sem símbolos associadas a eles.

Quais são os candidatos?

Com essa restrição em mente, as opções ficam bem definidas. Veja o que cada uma significaria na prática para trabalhar com bytes:

Símbolos diferentes
(Base)
Cada símbolo representaPara escrever 1 byte precisamos de
2
Binário
1 bit8 caracteres
4
Quaternário
2 bits4 caracteres
8
Octal
3 bits???
16
Hexadecimal
4 bits2 caracteres
32
Base-32
5 bits???

2 símbolos é o próprio binário. Já temos isso e também sabemos que 1 byte precisa de 8 bits. Não resolve nada.

4 símbolos: cada símbolo representaria um grupo de 2 bits dentro de um byte, logo, um byte precisaria de 4 caracteres. É mais compacto que o binário, mas ainda são 4 caracteres por byte. É uma melhora pequena, mas longe do ideal.

8 símbolos: cada símbolo representa 3 bits. Aqui está o problema do primeiro ???: 1 byte tem 8 bits, e 8 não é divisível por 3. Dois caracteres cobririam 6 bits, e faltariam outros 2 bits. Três caracteres cobririam 9 bits, sobraria 1. Não tem como ter um número inteiro de caracteres no sistema octal que represente exatamente um byte. A correspondência não fecha. Veremos mais adiante que esse sistema foi tentado mesmo assim, e os problemas que ele causou.

16 símbolos: cada símbolo representa um grupo de 4 bits. E o 8 dividido por 4 é exatamente 2, sem sobra nenhuma. Um byte vira sempre 2 símbolos. Sempre, sem exceção. A correspondência fecha perfeitamente!

32 símbolos: o mesmo problema do octal aparece de novo: cada símbolo representaria 5 bits, e 8 não é divisível por 5. E mesmo que fosse, 32 símbolos para aprender é muita coisa.

A opção de 16 símbolos é claramente o ponto de equilíbrio perfeito: compacto, com lógica interna, e com uma relação direta com os bytes. Mas antes de ir para ela, vale contar a história octal - porque esse sistema foi realmente usado por anos, e inclusive até hoje é usado em alguns cantos (como em algumas coisas no linux), e seja possível você acabar se deparando com ele. Além de que entender porque falhou ajuda a entender por que o hexadecimal sobreviveu.

A tentativa que quase funcionou: o octal

O sistema de 8 símbolos (também conhecido por base-8) tem um nome: octal. É um sistema de base 8, onde os símbolos são simplesmente os dígitos de 0 a 7.

E você deve estar se perguntando: se acabamos de ver que o octal não funciona com bytes, por que raios alguém o usou?

A resposta é que, por muito tempo, bytes não eram o padrão. Isso pode parecer estranho depois de tudo que você aprendeu neste capítulo, mas é verdade. Nos anos 1950 e boa parte dos 1960, os computadores não tinham uma unidade padrão de memória ainda. Diferentes máquinas trabalhavam com grupos de bits de tamanhos diferentes - algumas usavam 6 bits como unidade básica, outras usavam 12, outras usavam 36. Era uma época em que a indústria ainda estava descobrindo como construir computadores, e cada fabricante tomava suas próprias decisões. Esse é um exemplo perfeito de quando na subseção Como nasce um código, na seção 1.5, eu disse que essa época era "um caos" na computação.

Nesse cenário, o octal fazia todo sentido. O PDP-8 da DEC, por exemplo, lançado em 1965 e um dos computadores mais influentes da história, trabalhava com palavras de 12 bits, e 12 é divisível por 3. Cada símbolo octal representa 3 bits, a divisão fechava perfeitamente, e a conversão era tão mecânica quanto a gente precisa que seja. Manuais da época eram cheios de números octais. Programadores aprendiam a pensar em octal e funcionava bem.

Um PDP-8 em exposição no Museu Nacional da Computação em Bletchley, Inglaterra.
Um PDP-8 em exposição no Museu Nacional da Computação em Bletchley, Inglaterra. Kris Arnold, CC BY-SA 2.0, via Wikimedia Commons

Mas conforme o byte de 8 bits foi se tornando o padrão universal - como você já viu na seção 1.6, o problema matemático que já existia no octal ficou impossível de ignorar. 8 bits não se encaixava num sistema de divisão por 3, e nunca vai ser.

Isso significa que não existia nenhuma forma de fazer o octal funcionar limpo com bytes. Não era um problema que podia ser resolvido com uma convenção nova ou um padrão melhor. A incompatibilidade estava na raiz. Na relação entre o tamanho do símbolo e o tamanho do byte. E foi exatamente essa raiz que ajudou a entrar a solução certa: se o problema era que 3 bits por símbolo não encaixava em um byte, a pergunta que surgiu naturalmente foi quantos bits por símbolo encaixam. A resposta - mais conveniente, como vimos, era 4. E um sistema onde cada símbolo representa exatamente 4 bits tem exatamente 16 símbolos possíveis.

É aí que entra o hexadecimal.

A solução elegante: hexadecimal

Um sistema onde cada símbolo representa exatamente 4 bits. 4 bits por símbolo, 8 bits por byte, portanto 2 símbolos por byte. A conta fecha perfeitamente.

Esse sistema tem um nome: hexadecimal. Do grego hexa, que significa seis, e do latim decem, que significa dez. Dezesseis.

De onde vieram os 16 símbolos?

Agora surge a pergunta: quais são esses 16 símbolos?

O sistema decimal já nos dá 10 que todo mundo conhece e está acostumado: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9. Continuar usando esses seria uma ideia muito boa e tornaria mais fácil de aprender. Mas o hexadecimal precisa de 16. Faltam 6.

Aqui a decisão foi a mais simples possível. Lembra que descartamos a ideia de inventar símbolos novos porque ninguém os conheceria e não teriam lógica interna? A solução foi pegar os 6 símbolos restantes do alfabeto: A, B, C, D, E e F - símbolos que qualquer pessoa alfabetizada já conhecia e sabia escrever e saber de cabeça qual é maior (qual vem depois de qual), sem precisar aprender nada novo.

Se paramos no 9, a seguir vem o A, que representa dez. O B representa onze. o C representa doze. E assim por diante, até o F, que representa quinze. Não tem nenhum mistério nisso. É simplesmente a continuação dos digitos decimais onde eles acabam até o 15.

Uma dica importante sobre sistemas numéricos

No sistema decimal existem 10 símbolos possíveis: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

A contagem começa em 0, e o 0 também conta como símbolo. Por isso, mesmo indo apenas até 9, o sistema possui dez símbolos diferentes. Entretanto, não é por possuir 10 símbolos que significa que o maior valor seja 10. Na verdade, 10 já é um número formado por dois símbolos: 1 e 0.

O sistema hexadecimal segue exatamente a mesma lógica. Ele possui 16 símbolos diferentes: 0-9 e A-F. E é importante não confudir achando que a contagem vai até 15. Ela vai até 15. A contagem também começa em 0 e vai até o último símbolo (F), que representa o valor 15, não 16.

Decimal:

0 1 2 3 4 5 6 7 8 9

Hexadecimal:

0 1 2 3 4 5 6 7 8 9 A B C D E F

O resultado é a tabela abaixo. Guarda ela com carinho, porque o hexadecimal estará presente ao longo de quase toda sua vida como programador:

DecimalHexadecimalBinário
0000000 0000
1010000 0001
2020000 0010
3030000 0011
4040000 0100
5050000 0101
6060000 0110
7070000 0111
8080000 1000
9090000 1001
100A0000 1010
110B0000 1011
120C0000 1100
130D0000 1101
140E0000 1110
150F0000 1111
DecimalHexadecimalBinário
16100001 0000
17110001 0001
18120001 0010
19130001 0011
20140001 0100
21150001 0101
22160001 0110
23170001 0111
24180001 1000
25190001 1001
261A0001 1010
271B0001 1011
281C0001 1100
291D0001 1101
301E0001 1110
311F0001 1111
DecimalHexadecimalBinário
32200010 0000
33210010 0001
34220010 0010
35230010 0011
36240010 0100
37250010 0101
38260010 0110
39270010 0111
40280010 1000
41290010 1001
422A0010 1010
432B0010 1011
442C0010 1100
452D0010 1101
462E0010 1110
472F0010 1111
DecimalHexadecimalBinário
48300011 0000
49310011 0001
50320011 0010
51330011 0011
52340011 0100
53350011 0101
54360011 0110
55370011 0111
56380011 1000
57390011 1001
583A0011 1010
593B0011 1011
603C0011 1100
613D0011 1101
623E0011 1110
633F0011 1111
DecimalHexadecimalBinário
64400100 0000
65410100 0001
66420100 0010
67430100 0011
68440100 0100
69450100 0101
70460100 0110
71470100 0111
72480100 1000
73490100 1001
744A0100 1010
754B0100 1011
764C0100 1100
774D0100 1101
784E0100 1110
794F0100 1111
DecimalHexadecimalBinário
80500101 0000
81510101 0001
82520101 0010
83530101 0011
84540101 0100
85550101 0101
86560101 0110
87570101 0111
88580101 1000
89590101 1001
905A0101 1010
915B0101 1011
925C0101 1100
935D0101 1101
945E0101 1110
955F0101 1111
DecimalHexadecimalBinário
96600110 0000
97610110 0001
98620110 0010
99630110 0011
100640110 0100
101650110 0101
102660110 0110
103670110 0111
104680110 1000
105690110 1001
1066A0110 1010
1076B0110 1011
1086C0110 1100
1096D0110 1101
1106E0110 1110
1116F0110 1111
DecimalHexadecimalBinário
112700111 0000
113710111 0001
114720111 0010
115730111 0011
116740111 0100
117750111 0101
118760111 0110
119770111 0111
120780111 1000
121790111 1001
1227A0111 1010
1237B0111 1011
1247C0111 1100
1257D0111 1101
1267E0111 1110
1277F0111 1111
DecimalHexadecimalBinário
128801000 0000
129811000 0001
130821000 0010
131831000 0011
132841000 0100
133851000 0101
134861000 0110
135871000 0111
136881000 1000
137891000 1001
1388A1000 1010
1398B1000 1011
1408C1000 1100
1418D1000 1101
1428E1000 1110
1438F1000 1111
DecimalHexadecimalBinário
144901001 0000
145911001 0001
146921001 0010
147931001 0011
148941001 0100
149951001 0101
150961001 0110
151971001 0111
152981001 1000
153991001 1001
1549A1001 1010
1559B1001 1011
1569C1001 1100
1579D1001 1101
1589E1001 1110
1599F1001 1111
DecimalHexadecimalBinário
160A01010 0000
161A11010 0001
162A21010 0010
163A31010 0011
164A41010 0100
165A51010 0101
166A61010 0110
167A71010 0111
168A81010 1000
169A91010 1001
170AA1010 1010
171AB1010 1011
172AC1010 1100
173AD1010 1101
174AE1010 1110
175AF1010 1111
DecimalHexadecimalBinário
176B01011 0000
177B11011 0001
178B21011 0010
179B31011 0011
180B41011 0100
181B51011 0101
182B61011 0110
183B71011 0111
184B81011 1000
185B91011 1001
186BA1011 1010
187BB1011 1011
188BC1011 1100
189BD1011 1101
190BE1011 1110
191BF1011 1111
DecimalHexadecimalBinário
192C01100 0000
193C11100 0001
194C21100 0010
195C31100 0011
196C41100 0100
197C51100 0101
198C61100 0110
199C71100 0111
200C81100 1000
201C91100 1001
202CA1100 1010
203CB1100 1011
204CC1100 1100
205CD1100 1101
206CE1100 1110
207CF1100 1111
DecimalHexadecimalBinário
208D01101 0000
209D11101 0001
210D21101 0010
211D31101 0011
212D41101 0100
213D51101 0101
214D61101 0110
215D71101 0111
216D81101 1000
217D91101 1001
218DA1101 1010
219DB1101 1011
220DC1101 1100
221DD1101 1101
222DE1101 1110
223DF1101 1111
DecimalHexadecimalBinário
224E01110 0000
225E11110 0001
226E21110 0010
227E31110 0011
228E41110 0100
229E51110 0101
230E61110 0110
231E71110 0111
232E81110 1000
233E91110 1001
234EA1110 1010
235EB1110 1011
236EC1110 1100
237ED1110 1101
238EE1110 1110
239EF1110 1111
DecimalHexadecimalBinário
240F01111 0000
241F11111 0001
242F21111 0010
243F31111 0011
244F41111 0100
245F51111 0101
246F61111 0110
247F71111 0111
248F81111 1000
249F91111 1001
250FA1111 1010
251FB1111 1011
252FC1111 1100
253FD1111 1101
254FE1111 1110
255FF1111 1111

Olha com atenção para a coluna do binário. Cada símbolo hexadecimal corresponde exatamente a 4 bits. O 0 é sempre 0000. O F é sempre 1111. O A é sempre 1010. Essa correspondência não muda nunca, independente do contexto ou da posição do número.

E é exatamente isso que torna a conversão mecânica. Você não precisa calcular nada. Não tem conta. Não tem divisão. Não tem multiplicação. Você olha para um símbolo hexadecimal e sabe imediatamente quais 4 bits ele representa. Você olha para 4 bits e sabe imediatamente qual símbolo hexadecimal é. Para um computador fazer automaticamente agora era mais fácil. Não era mais necessário fazer centenas de contas para entregar o resultado convertido em decimal. Se você quisesse o resultado em hexadecimal, o computador só precisaria fazer substituições.

Como converter na prática

Converter um byte de binário para decimal é um processo de dois passos:

  1. Divide o byte em dois grupos de 4 bits.
  2. Consulta a tabela e substitui cada grupo pelo símbolo correspondente.

Vamos usar o primeiro byte daquele memory dump no início da seção: 01011001. Lembra dele? Em decimal, você precisou de 8 multiplicações e 8 somas para descobrir que era o número 89, com risco silencioso em cada uma das operações.

Em hexadecimal:

  1. Divide: 01011001 vira 0101 1001.
  2. Consulta o equivalente em hexadecimal:
    • 0101 vira 5.
    • 1001 vira 9.
  3. Resulado em hexadecimal: 59.
BinárioDecimalHexadecimal
010110018959

Viu? É muito simples! Dois passos. Dois segundos. Praticamente zero risco de erros silenciosos.

O caminho de volta, de hexadecimal para binário é igualmente fácil. Você só faz o processo ao contrário:

  1. Divide: 59 vira 5 9.
  2. Consulta o equivalente em hexadecimal:
    • 5 vira 0101.
    • 9 vira 1001.
  3. Resultado em binário: 01011001.

É só isso. Não tem mais nada escondido. Toda complexidade que existia na conversão decimal simplesmente desaparece, substituída por uma tabela que, com um pouco de prática, você passa a fazer de cabeça sem nem precisar olhar para ela.

Relendo o memory dump

Agora que você sabe como o hexadecimal funciona, vamos voltar àquele memory dump do início da seção. Lembra dele? Aquele que você recebeu depois de um café da manhã, cheio de zeros e uns.

Aqui está o mesmo dump, mas desta vez em hexadecimal:

59 6F 75 20 61 72 65 20 6C 65 61 72 6E 69 6E 67
20 52 75 73 74 20 77 69 74 68 20 74 68 65 20 62
65 73 74 20 72 65 73 6F 75 72 63 65 20 65 76 65
72 3A 20 55 6C 74 69 6D 61 74 65 20 52 75 73 74
20 28 68 74 74 70 73 3A 2F 2F 75 72 75 73 74 2E
6F 72 67 29 2E 20 42 65 6C 6F 77 20 69 73 20 61
6E 20 65 78 61 6D 70 6C 65 20 6F 66 20 61 20 63
6F 72 72 75 70 74 65 64 20 74 65 78 74 20 74 68
61 74 20 70 72 6F 67 72 61 6D 6D 65 72 73 20 73
6F 6D 65 74 69 6D 65 73 20 68 61 76 65 20 74 6F
20 64 65 62 75 67 3A 20 EF BF BD EF BF BD
Caso você não se lembre, ele era assim em binário
01011001 01101111 01110101 00100000 01100001 01110010 01100101 00100000 01101100 01100101 01100001 01110010 01101110 01101001 01101110 01100111
00100000 01010010 01110101 01110011 01110100 00100000 01110111 01101001 01110100 01101000 00100000 01110100 01101000 01100101 00100000 01100010
01100101 01110011 01110100 00100000 01110010 01100101 01110011 01101111 01110101 01110010 01100011 01100101 00100000 01100101 01110110 01100101
01110010 00111010 00100000 01010101 01101100 01110100 01101001 01101101 01100001 01110100 01100101 00100000 01010010 01110101 01110011 01110100
00100000 00101000 01101000 01110100 01110100 01110000 01110011 00111010 00101111 00101111 01110101 01110010 01110101 01110011 01110100 00101110
01101111 01110010 01100111 00101001 00101110 00100000 01000010 01100101 01101100 01101111 01110111 00100000 01101001 01110011 00100000 01100001
01101110 00100000 01100101 01111000 01100001 01101101 01110000 01101100 01100101 00100000 01101111 01100110 00100000 01100001 00100000 01100011
01101111 01110010 01110010 01110101 01110000 01110100 01100101 01100100 00100000 01110100 01100101 01111000 01110100 00100000 01110100 01101000
01100001 01110100 00100000 01110000 01110010 01101111 01100111 01110010 01100001 01101101 01101101 01100101 01110010 01110011 00100000 01110011
01101111 01101101 01100101 01110100 01101001 01101101 01100101 01110011 00100000 01101000 01100001 01110110 01100101 00100000 01110100 01101111
00100000 01100100 01100101 01100010 01110101 01100111 00111010 00100000 11101111 10111111 10111101 11101111 10111111 10111101

Dica

Não se sinta assustado com a feiura toda que o amontoado de hexadecimais e binários parecem ser. A feiura faz parecer complexo, mas não é. Você não precisa olhar para isso e saber de cara o que significa. Ninguém sabe, não se preocupe.

O que acontece de fato é tudo que já aprendemos até aqui - você precisaria pegar cada byte (agora representado por grupos de dois hexadecimais), verificar na tabela qual a sua equivalência, e só depois aó sim você saberia o que significa. É assim mesmo.

É o mesmo conteúdo de antes. Mas olha a diferença. Cada byte agora ocupa exatamente 2 caracteres.

Os espaços repetem muitas vezes - você consegue ver o 20 aparecendo várias vezes, e se você cruzar com a tabela ASCII da seção 1.5, vai reconhecer: 20 é 32 em decimal, que é o espaço. Sem fazer nenhuma conta, só consultando a tabela hexadecimal que você acabou de aprender. No final você consegue reparar EF BF BD aparecendo duas vezes, e se você fosse um programador com um pouco mais de experiência em ler hexadecimal, você iria acabar aprendendo que esses caracteres representam um código unicode para erro - aquele símbolo que aparece quando um texto está corrompido. Você consegue ver isso de bater o olho, sem multiplicação, sem divisão.

Um programador experiente olhando para esse dump consegue extrair informações em segundos. Não é porque é um gênio. É pelo mesmo motivo pelo qual um médico que passa anos lendo eletrocardiogramas consegue bater um olho numa linha e já sentir que algo está errado, ou um músico que lê partituras há décadas consegue ouvir a música na cabeça sem tocar uma única nota, ou um contador experiente que olha para uma coluna de números e percebe que algo não fecha antes mesmo de somar qualquer coisa. Nenhum deles nasceu sabendo. O hexadecimal torna esses padrões visíveis o suficiente para o cérebro fazer o seu trabalho.

Hexadecimal está em todo lugar

O hexadecimal nasceu da necessidade de tornar memory dumps legíveis nos anos 1960. Mas resolveu o problema tão bem que sobreviveu até hoje e se tornou a forma padrão de representar qualquer valor que precise ficar próximo do nível dos bytes - não só em depuração, mas em toda programação moderna. Você provavelmente já esbarrou nele várias vezes sem saber o que era. Vamos ver onde.

As cores

Olhe para a tela que você está usando agora. Pode ser um monitor, um celular, um tablet - não importa. O que todas essas telas têm em comum é que a imagem que você vê é formada por milhões de pontinhos minusculos chamados pixels. Cada pixel é um quadradinho tão pequeno que você não consegue ver individualmente a olho nu, mas juntos eles formam tudo que aparece na tela.

Cada pixel não é uma coisa só. Cada pixel é formado por três luzes microscópicas, uma ao lado da outra - uma vermelha, uma verde, e uma azul. O que você enxerga como "uma cor" é na verdade a mistura dessas três luzes com intensidades diferentes. Esse modelo se chama RGB, do inglês Red, Green, Blue, que significa literalmente Vermelho, Verde, Azul.

Parece confuso? Pense assim: se você ligar o vermelho no máximo, o verde no máximo e o azul no máximo, as três luzes juntas formam branco. Se você desligar as três, você tem preto. Se você ligar só o vermelho no máximo e deixar os outros dois desligados, você tem vermelho puro. E variando a intensidade das três luzes, você consegue produzir praticamente qualquer cor que o olho humano consegue distinguir.

Mas ligar no máximo e desligar são apenas os extremos. Cada componente pode ter qualquer intensidade entre o 0 e o máximo. E a pergunta que pode aparecer é: quantos níveis de intensidade usar?

Agora que você já entende como informação funciona dentro do computador, vai ficar bem fácil entender essa. Cada pixel tem cada cor armazenada em 1 byte, por tanto, cada cor pode variar entre 0 (desligada) e 255 (máximo). Cada píxel ocupa exatamente 3 bytes na memória: um para o vermelho, um para o verde e um para o azul.

256 níveis de intensidade para cada cor resulta em 256 x 256 x 256 = mais de 16 milhões de cores distintas. O olho humano consegue distinguir em torno de 10 milhões de cores. Ou seja, 256 níveis de intensidade por cor é mais do que suficiente para reproduzir qualquer cor que você seria capaz de perceber.

E é aqui que o hexadecimal entra de forma completamente amigável. Quando você vê uma cor escrita como #FF5733, você está vendo três bytes em hexadecimal, um para cada componente:

  • FF é a intensidade do vermelho - 255 em decimal, intensidade máxima.
  • 57 é a intensidade do verde - 87 em decimal.
  • 33 é a intensidade do azul - 51 em decimal.

O resultado é a cor abaixo - que você pode clicar para brincar com os tons de RGB (vermelho, verde e azul):

Toda vez que um programador precisa definir uma cor no código - seja para um site, um aplicativo, um jogo, qualquer coisa com interface visual - ela é escrita exatamente assim: três bytes em hexadecimal, precididos de #. Você vai fazer isso muitas vezes ao longo do Ultimate Rust, e com o tempo vai se tornar algo tão natural quanto escrever qualquer outro número.

A notação unicode

Na seção 1.6, você aprendeu que o Unicode atribui um número único a cada caractere de todos os idiomas e sistemas de escrita do mundo. Esse número é chamado de code point. Você já tinha visto que o A é o code point 65. O ã é o code point 227. O 🌍 é o code point 127757.

Agora, como você escreve esses números quando precisa se referir a um caractere específico quando estiver programando e o caractere não tem no seu teclado? Ou numa documentação, numa mensagem de erro, discussão técnica? Em decimal você poderia escrever 65, 227 e 127757. Mas a comunidade Unicode adotou uma notação diferente desde o início: os code points são escritos em hexadecimal, precedidos de U+.

Então o A vira U+0041. O ã vira U+00E3. O 🌍 vira U+1F30D.

Você deve estar se perguntando duas coisas. Primeira: por que em hexadecimal? E você já sabe a resposta. Hexadecimal tem uma relação direta com bytes, e code points são valores que eventualmente precisam ser representados em bytes na memória. Escrever U+00E3 em vez de U+227 não é só uma questão de gosto. Lembra que par de dígitos hexadecimais corresponde a 1 byte? Aqui é a mesma coisa. Quando você olha para U+00E3, você já está olhando para bytes por baixo - e isso vai fazer cada vez mais sentido conforme você for aprendendo como textos são armazenados na memória ao longo do Ultimate Rust.

Segunda pergunta: por que U+0041 e não simplesmente U+41? Afinal, 41 em hexadecimal é a mesma coisa que 0041 - os zeros à esquerda não mudam nada matematicamente. A razão é simplesmente por convenção. O Unicode foi projetado para acomodar mais de um milhão de caracteres, e os code points podem ter até 6 dígitos hexadecimais. Para tornar os valores comparáveis e fáceis de ler lado a lado, a convenção é sempre usar no minínmo 4 dígitos, preenchidos com zeros à esquerda quando necessário. Assim, U+0041 e U+1F30D ficam visualmente alinhados.

É o mesmo motivo pelo qual em horários os minutos são escritos como 09:05 e não 9:5. Os zeros não mudam o significado, mas tornam a leitura mais rápida e menos propensa a erros.

Você vai ver a notação U+XXXX constantemente ao longo da sua carreira. Agora você sabe exatamente o que ela significa e por que foi escolhida dessa forma.

Endereços de memória e o prefixo 0x

Toda vez que seu programa guarda um valor na memória, esse valor fica guardando em algum endereço, como você aprendeu na subseção "O computador não pensa em bits, pensa em bytes", da seção anterior. Um endereço é um número que indica exatamente onde na memória aquele dado está, como o número de uma casa numa rua. E esses endereços são sempre escritos em hexadecimal.

Quando você programar em Rust - e isso vai acontecer mais cedo do que você imagina - vai se deparar com endereços assim: 0x7FFE4B2A. Ou com constantes assim: 0xFF, 0x00E3, 0x1F30D.

O 0x que aparece antes do número é a convenção adotada por praticamente todas as linguagens de programação modernas para avisar: "o que vem a seguir está em hexadecimal". O 0x não faz parte do valor em si, tal como o U+ no Unicode e o # nas cores também não fazem. Em Rust, escrever 255 e 0xFF é exatamente a mesma coisa para a linguagem. Qual usar é uma escolha sua, que vai depender do contexto.

Essa escolha importa mais do que parece. Pense bem: você passou a seção inteira aprendendo que o problema do decimal é que ele não tem relação direta com bits. O hexadecimal resolve exatamente isso. Então quando você está escrevendo código que "trabalha próximo da memória" - e isso vai acontecer - usar 0xFF em vez de 255 não é só uma preferência estética. É uma convenção. Usar 255 funcionaria, mas quem estiver lendo o seu código espera encontrar 0xFF quando se trata de bytes realmente.

Lembra do que falamos sobre tornar o seu código mais legível para outras pessoas? Esse é um exemplo. É uma escolha que torna o código mais fácil de entender. Quem vê 0xFF sabe imediatamente, sem calcular nada, que todos os 8 bits desse byte estão ligados - porque F é 1111, e você tem 2 Fs, um para cada metade do byte. Quando você vê 255, você precisa primeiro lembrar que 255 é o valor máximo de um byte, e aí concluir que todos os bits estão ligados. São dois passos em vez de um.

Fique tranquilo!

Com o tempo e com a prática, ler hexadecimal vai se tornar cada vez mais natural, exatamente como aconteceu com a leitura das letras quando você era criança. No começo era lento e trabalhoso. Hoje você lê sem pensar. Com hexadecimal vai ser a mesma coisa.

Bytes precisam de contexto

Você agora consegue olhar para um memory dump em hexadecimal e extrair informações dele. Sabe que o 20 é um espaço, que EF BF BD é um caractere Unicode corrompido, que FF é um byte com todos os bits ligados. O hexadecimal cumpriu o que prometeu: tornou bytes legíveis.

Mas para um olho ainda não treinado, mesmo o dump em hexadecimal é só uma sequência de números e letras estranhas. E isso releva algo importante: saber ler os bytes não é suficiente. Você também precisa saber o que aqueles bytes representam, e isso não está escrito nos bytes em si.

Se eu lhe mostrar a sequência FF D8 FF EO, você consegue ler os quatro bytes perfeitamente em hexadecimal. Mas o que eles representam? Um número? Um texto? um trecho de áudio? Parte de uma imagem?

A resposta é: depende. Depende de quem criou esses bytes, com que intenção, e - mais importante - de quem está lendo e como está interpretando. Os bytes não carregam essa informação dentro deles. Eles são apenas números em algum lugar da memória. O significado vem de fora.

Esse é o ponto que o capítulo inteiro estava tentando construir sem nomear diretamente. Você aprendeu que os computadores só guardam zeros e uns. Aprendeu que zeros e uns formam bytes. Aprendeu que bytes podem representar números, letras, code points, endereços, cores. Mas em nenhum momento os bytes te disseram o que eram. Eu te disse. Você precisou de uma tabela, de uma convenção, de um contexto externo para interpretar cada um deles.

Uma foto, uma música, um documento de texto - todos são apenas sequências de bytes. O que os diferencia não está nos bytes em si. Está no contexto que existe em torno deles.

E quem gerencia esse contexto no seu computador é o assunto da próxima seção.

Antes de avançar

Você consegue responder:

  1. Por que o hexadecimal foi adotado para representar bytes, em vez de decimal ou binário diretamente?
  2. Dado o byte 10110100, como você o converteria para hexadecimal? E o caminho inverso: dado C7, quais são os bits?
  3. Por que bytes sozinhos não são suficientes para saber o que um dado representa?

Se consegue responder as três, está pronto para a última seção do capítulo.