Extremidade (ordenação) – Wikipédia, a enciclopédia livre

Em computação, extremidade (tradução literal de endianness, que também pode significar fim, término) refere-se à ordem utilizada para representar determinado tipo de dado.

O termo em inglês para uma forma de endianness, big-endian, é uma referência às Viagens de Gulliver: em Lilliput houve uma guerra civil, entre os que preferiam quebrar os ovos cozidos pelo lado maior (big-endians - termo traduzido como "pontagrandenses" nas edições em português) contra quem preferia quebrar os ovos cozidos pelo lado menor. Este conflito, por sua vez, era uma paródia entre as diferenças entre católicos e protestantes a respeito da transubstanciação.

Como caso típico temos a ordem como os números inteiros são guardados na memória em grupos sequenciais de bytes e a ordem utilizada para transmitir sucessivos bytes pela Internet ou qualquer outro meio.

Quando falamos especificamente em bytes, o termo inglês endianness (extremidade) é referido simplesmente como ordem dos bytes.[1]

De forma genérica, extremidade é o formato de representação - qual o byte de um caracter UCS-2 deve ser guardado no endereço inferior, etc. A ordem dos bytes é extremamente importante na programação para redes, dado que dois computadores com ordem de armazenamento de bytes diferentes, ao comunicarem, dão origem a corrupção de dados e a mensagem torna-se ininteligível no destino.

Analogia[editar | editar código-fonte]

Extremidade é a forma de ordenar dados pela sua ordem de magnitude ou ordem de grandeza. Como analogia, nas línguas europeias, os números são representados na ordem extremidade maior primeiro (big-endian), em que os primeiros dígitos a serem representados são os de maior peso. Por exemplo, o número 1234 significa 1 milhar (10³), 2 centenas (10²) 3 dezenas (101) e 4 unidade (100). Datas escritas no formato big-endian devem ser escritas como AAAA-MM-DD. O formato DD-MM-AAAA é a representação da data em little-endian, e finalmente o formato MM-DD-AAAA é a representação em middle-endian, porque o campo com a ordem de grandeza média está escrito primeiro. No entanto, os números que representam (individualmente) os dias, meses e anos estão todos no formato big-endian.

A ordenação de bytes e o hardware[editar | editar código-fonte]

A maioria dos processadores lê e escreve os bytes na memória principal, com os bits "dentro" dos bytes individuais quase sempre na mesma ordem (embora haja raríssimas excepções). Isto significa que um byte será lido ou escrito, da mesma forma em praticamente todos os sistemas digitais (computadores pessoais mainframes, sistemas de automação e controlo e até consoles de jogos).

Os números inteiros são normalmente guardados em sequências de bytes, sendo que o valor é obtido por simples concatenação. Os dois métodos mais comuns são:

  • Os bytes são guardados por ordem crescente do seu "peso numérico" em endereços sucessivos da memória (extremidade menor primeiro ou little-endian).
  • Os bytes são guardados por ordem decrescente do seu "peso numérico" em endereços sucessivos da memória (extremidade maior primeiro ou big-endian).

Os processadores mais conhecidos que utilizam o formato little-endian são os da Intel (x86), AMD, Zilog (Z80), MOS Technology (6502), DEC (VAX e PDP-11). No caso dos processadores que utilizam o formato big-endian, os mais conhecidos são os da Motorola (famílias 6800 e 68000). O PowerPC que integrava os Apple Macintosh antes da mudança para os processadores da Intel, bem como o System/370. O SPARC historicamente usou sempre big-endian, embora a versão 9 seja bi-endian (ver abaixo).

Hardware bi-endian[editar | editar código-fonte]

Algumas arquitecturas de hardware (incluindo a ARM, PowerPC (excepto PPC970/G5), DEC Alpha, SPARC V9, MIPS, PA-RISC e IA64) implementam ordenação comutável (em inglês switchable endianness). Esta característica melhora o desempenho e simplifica a lógica dos dispositivos físicos de rede, bem como o respectivo software. A designação bi-endian, relativa ao hardware, denota a capacidade de processar ou enviar dados em qualquer dos dois formatos.

Algumas destas arquitecturas podem ser alteradas via software para um formato específico (normalmente quando o computador arranca); no entanto, em alguns sistemas o formato default é seleccionado por hardware na placa-mãe e não pode ser alterado por software. (e.g., o DEC Alpha, funciona apenas no modo big-endian Cray T3E.)

De salientar que o termo "bi-endian" se refere principalmente ao modo como o processador acede aos dados. O acesso às instruções num determinado processador pode ter um formato de ordem de pesquisa fixo e pré-determinada, mesmo que o acesso aos dados seja bi-endian.

De salientar também, alguns processadores "bi-endian" podem, na realidade utilizar "magia" internamente (ao contrário de comutação real para outro tipo de ordenação de bytes) para um dos seus modos de funcionamento. Por exemplo, alguns processadores PowerPC no modo little-endian agem como little-endian do ponto de vista da execução de programas, mas não guardam os dados em memória no formato (valores multi-byte são trocados durante o processo de leitura/escrita da/na memória). Isto pode causar problemas quando o conteúdo da memória é transferido para um dispositivo externo, se alguma parte do driver, não tem em conta essa situação.

Transferência de dados entre processador e memória[editar | editar código-fonte]

Tipos de ordenação de bytes.

Os processadores actuais, têm todos (salvo casos pontuais) um barramento de dados de 64 bits, embora os seus registadores internos se mantenham com 32 bits e por isso, se continuem a classificar como processadores de 32 bits. Numa acesso a 64 bits, são usados internamente dois registadores de 32 bits.
Por isso a transferência de dados entre o processador e a memória pode ser feita a 8, 16, 32 ou 64 bits. Neste diagrama podemos ver como essa transferência de dados se faz em ambos os modos: extremidade menor primeiro (little endian) e extremidade maior primeiro (big endian). Para ajudar a entender o diagrama, no modo extremidade menor primeiro os endereços de memória crescem da direita para a esquerda e no modo extremidade maior primeiro os endereços de memória crescem da esquerda para a direita.

No entanto, para os não entendidos em hardware, deve ser salientado o seguinte: Isto é apenas uma representação gráfica, para ajudar a explicar o funcionamento da transferência de dados. Na realidade, na construção física das memórias, não existe este conceito de "direcção". As memórias têm um barramento de endereços onde se coloca um padrão de bits que define a célula da memória a que queremos ter acesso, e um barramento de dados onde se coloca um byte (8 bits) (no caso de acesso para escrita), e de onde se lê um byte (no caso de acesso para leitura).

Se o acesso for para múltiplos de um byte - 2 (16 bits), 4 (32 bits) ou 8 (64 bits), poderão ter necessários vários acessos à memória, caso aconteça uma das situações (ou ambas):

  • A memória estar organizada com menos de 64 bits de barramento de dados (8, 16 ou 32)
  • O alinhamento dos bytes na memória não estar optimizado.

Deve ser referido, que as transferências de dados entre o processador e a memória, são feitos sempre em potências de dois: 1 byte = 20, 2 bytes = 21, 4 bytes = 2², 8 bytes = 2³.

Exemplos[editar | editar código-fonte]

Armazenamento do valor 0x0A0B0C0D na memoria[editar | editar código-fonte]

Nota: O prefixo 0x indica notação hexadecimal.

Para ilustrar ainda melhor, mostram-se exemplos de um número inteiro de 32 bits, guardado na memória, nos tipos de ordenação de bytes mais comuns. Nem todas as plataformas usam os formatos representados mas, na prática, são muito poucas as excepções.

Os exemplos referem-se ao armazenamento em memória RAM do valor 0x0A0B0C0D. A razão porque se salienta o facto da memória ser do tipo RAM, é porque as acções representadas, referem-se a escrita de dados. No caso das memórias do tipo ROM (e similares), só se podem efectuar acções de leitura (da memória para o processador). No entanto, em qualquer dos casos (leitura ou escrita), os diagramas aplicam-se, tendo apenas em conta que no caso de leitura de dados, as setas devem ser desenhadas na direcção contrária. Exceptuando isto, tudo o resto é igual.

Verificação do formato de ordenação de bytes[editar | editar código-fonte]

A linguagem assembly, é de longe, a mais poderosa, mais rápida, mais flexível e que permite tirar partido de todas as características de um microprocessador. No entanto, todo este poder de controle sobre o processador tem um preço: Para a maioria das aplicações, programar em assembly seria uma tarefa "titânica", razão pela qual foram criadas linguagens designadas de alto nível (porque estão mais próximas do raciocínio do programador), que facilitam muito a tarefa ao programador para a maioria das tarefas.

De qualquer forma, existem sempre casos em que não podemos prescindir da Linguagem Assembly, nomeadamente:

  • Software que controla directamente o hardware (BIOS, drivers, etc.).
  • Software para automação e controlo.

Além disso (apesar de em muitos casos não ser preciso), só quem tenha sólidos conhecimentos de Electrónica Digital, tem acesso a todas as funcionalidades dos microprocessadores. Sem entrar em demasiados detalhes, apresenta-se a seguir um segmento de código escrito em Linguagem Assembly, que mostra bem o modo como o processador manipula números inteiros em 64 bits.

Em primeiro lugar, devemos criar um número suficientemente grande, para ocupar 64 bits (8 bytes). Por outro lado, o número deve ser de fácil memorização (1 num byte, 2 no seguinte, 3 depois, etc. até chegar a 8 no último.) Como temos de criar um número em base 2, criamos a sequência 8, 7, 6, 5, 4, 3, 2, 1; em seguida, construímos um número em base 2 baseado nos números anteriores. a operação fica:

Abaixo segue um programa em assembly que executa a ação i = 72.623.859.790.382.856, escrito para processadores Intel (Pentium Pro e superiores), AMD e compatíveis:

Endereço Instrução (opcode + dados) Mnemónica / Directiva Comentários
.686P ; código para os processadores Pentium Pro e
superiores que permite utilizar todas as instruções
(incluindo em modo privilegiado)
.MODEL FLAT ; modelo de utilização de memória com o
máximo de desempenho (velocidade e flexibilidade)
00 00 00 42 C7 05 00 00 00 00 08 07 06 05 MOV DWORD PTR i, 84281096 ; 05060708H
00 00 00 4C C7 05 04 00 00 00 04 03 02 01 MOV DWORD PTR i+4, 16909060 ; 01020304H

Reparando bem na tabela, a coluna da esquerda representa os endereços de memória onde o programa corre e a coluna a seguir as instruções (que são constituídas pelo código nativo do processador e os dados). Se repararmos com atenção, vê-se que nos endereços 00 00 00 42 e 00 00 00 43 temos respectivamente C7 e 05 que são dois bytes que representam a instrução que está indicada na terceira coluna (MOV DWORD PTR i). Nos bytes 00 00 00 44 a 00 00 00 47 temos em todos 00 (endereço onde se encontra a variável i e nos endereços 00 00 00 48 a 00 00 00 4B temos os bytes 08, 07, 06, 05.

Analogamente, se compararmos com a segunda linha, vemos que depois do código da segunda instrução C7 05 vem 04 00 00 00 (endereço de memória onde fica guardada a segunda metade dos 64 bits) e que têm por conteúdo 04, 03, 02, 01.

Tiram-se assim duas conclusões importantes:

  1. O processador utiliza extremidade menor primeiro (little-endian)
  2. O processador, apesar de processar dados de 64 bits, não consegue fazê-lo com uma única instrução em código nativo. São precisas duas instruções-máquina (MOV DWORD PTR, em que cada uma move 32 bits), para copiar 64 bits de dados entre duas localizações na memória.

A seguir, apresenta-se em C++ um programa que faz o mesmo teste. No entanto, apesar do C++ ser uma linguagem de programação adequada para desenvolver aplicações que manipulem o hardware, não nos consegue informar que os 64 bits são escritos e/ou lidos com duas instruções em código nativo, gastando um total de 20 bytes, nem qual o tempo de execução das duas instruções, tendo em conta a frequência a que trabalha o microprocessador.

Explicação do programa em C++:

  1. Atribuímos à variável i um número inteiro de 64 bits.
  2. Criamos um apontador e atribuímos-lhe o valor do endereço em memória da variável i.
  3. Convertemos o endereço do apontador de inteiro a 64 bits para apontador de caracteres.
  4. Mostramos o número em sistema decimal (base 10) e sistema hexadecimal (base 16), para ser possível identificar os oito bytes separados.
  5. Mostramos sequencialmente por ordem crescente os endereços de memória onde os onde bytes residem, e os bytes que lá estão guardados.

A conclusão é que os bytes aparecem, começando no de menor peso e acabando no de maior peso, e que nos leva a concluir que, o processador onde este programa foi executado utiliza o método extremidade menor primeiro (little-endian).

Nota: Os endereços que se mostram diferem de computador para computador, já que os computadores não têm todos o mesmo mapa de endereçamento de memória, e podem estar a executar simultâneamente mais programas, o que faz com que a localização do programa na memória seja diferente. No entanto, todos os computadores que tenham um processador equivalente, mostrarão a mesma ordem de visualização dos bytes, mesmo que os endereços em que residem sejam diferentes.

Uso em diferentes meios[editar | editar código-fonte]

Máquinas virtuais[editar | editar código-fonte]

As máquinas virtuais mais conhecidas são (por ordem cronológica de aparecimento) a JVM (também conhecida por JRE) e o .NET. Como o objectivo de uma máquina virtual é tornar o código independente de plataforma, a ordenação é feita de acordo com o critério da empresa que a implementou. Para JVM, a ordenação de bytes segue a extremidade maior primeiro, enquanto para .NET a ordenação segue a extremidade menor primeiro.

Um pormenor que deve ser esclarecido, é que a ordenação de bytes pode ser diferente na máquina virtual e no processador em que ela corre. É este o caso de um programa escrito em linguagem Java a correr numa máquina com um processador Intel. O programa em Java utiliza a ordenação big-endian da máquina virtual, que por sua vez corre em little-endian no processador. A acção oposta também se pode verificar, desde que se utilize outra máquina virtual a correr sobre outro processador.

Embora a maior parte dos programadores não note diferença, em situações onde seja necessário efectuar cálculo científico muito intensivo, o cenário ideal é aquele em que, quer a máquina virtual quer o processador, tenham o mesmo modo de ordenação de bytes, para evitar que a conversão entre diferentes forma de ordenação de bytes se repita intensivamente, uma sobrecarga.

Redes de comunicação[editar | editar código-fonte]

Nas redes de comunicação de dados (termo genérico que abarca sob a mesma designação, redes de computadores e redes de telecomando - utilizadas em controlo remoto e automação), os controladores de comunicações existem em três tipos diferentes: USRT, UART e USART. Posteriormente apareceram os controladores de rede.

Na camada física do modelo OSI, todo o controlo da comunicação é feito por hardware e os bits são sempre transmitidos no formato little-endian. Nos três primeiros tipos de controladores de comunicações, é muito utilizada a norma EIA RS-232C, e no caso dos controladores de redes, utiliza-se sistemas mais sofisticados de comunicação. Entre eles, a utilização de códigos auto-sincronizáveis. Para situações específicas, alguns fabricantes utilizam um ASIC, e neste caso, a ordem pela qual os bits são enviados (entre outros parâmetros) é própria do fabricante do ASIC.

A partir da camada de enlace do modelo OSI, embora a ordem de envio dos bytes não seja relevante (desde que emissor e receptor usem o mesmo formato de ordenação), é mais comum utilizar o formato big-endian. Neste caso, designa-se o formato utilizado como ordem de rede. A razão histórica pela qual é este o formato o mais usado, prende-se com o facto de ser assim possível fazer o encaminhamento de dados sobre uma rede telefónica comutada utilizando o formato tradicional da composição de números de telefone. De facto, o Protocolo de Internet define uma norma big-endian designada por ordem de byte da rede. Esta ordem de bytes é usada para todos os valores numéricos no cabeçalho do pacote IP e por muitos protocolos de nível superior e formatos de ficheiros que são desenhados para funcionar sobre IP. Os soquetes Berkeley na sua API definem um conjunto de funções para converter inteiros de 16 e 32 bits de/para ordenação de dados em rede. As funções htons (host-to-network-short) e htonl (host-to-network-long) convertem valores em 16 e 32 bits respectivamente do computador (host) para a rede (network); enquanto as funções ntohs (network-to-host-short) e ntohl (network-to-host-long) convertem valores em 16 e 32 bits respectivamente da rede (network) para o computador (host).

Ficheiros[editar | editar código-fonte]

Todas as linguagens de programação (inclusive assembly) podem, se necessário, utilizar ficheiros para guardar de forma permanente, dados que na memória central seriam perdidos ao desligar o sistema. No que diz respeito ao seu conteúdo, os ficheiros podem ser de texto ou binários.

Nos ficheiros de texto, como o objectivo é escrever os dados de forma legível para o utilizador caso queira processar os dados num editor de texto, os dados são sempre gravados no formato big-endian. No que diz respeito aos ficheiros binários, a ordenação de bytes segue sempre a ordenação do processador utilizado. Aliás, não fazia sentido ser de outra forma, já que seria necessário haver inversão de bytes antes da acção de escrita e depois da acção de leitura.

No entanto, existem casos de linguagens de programação em que a ordenação de bytes é indicada de forma explicita. Um caso concreto em que isso acontece é na linguagem FORTRAN. Nalgumas implementações dela, quando se gravam dados em ficheiros binários, a ordenação de bytes pode ser expressamente indicada, tal como de indica a seguir:

OPEN (unidade, CONVERT='LITTLE_ENDIAN',...) OPEN (unidade, CONVERT='BIG_ENDIAN',...) 

No primeiro caso é aberto um ficheiro binário em modo little-endian e no segundo em big-endian.

Casos específicos[editar | editar código-fonte]

Ordenação de bytes com vírgula flutuante[editar | editar código-fonte]

Tal como acontece com os números inteiros, os números em vírgula flutuante (reais) também podem estar representados em little-endian ou big-endian.

Ordenação de bits[editar | editar código-fonte]

No que diz respeito à ordenação de bits, a questão quase não se coloca, dado que, os microprocessadores não conseguem aceder a submúltiplos do byte. Apenas os microcontroladores conseguem aceder a bits individuais, e neste caso particular, a posição dos bits é irrelevante. Só faz sentido falar em ordenação de bits em comunicação serial.

Referências

  1. Para hardware, existe um ficheiro de jargão que refere a menos comum expressão byte sex [1]. Não é explícito como esta terminologia é usada quando são possíveis mais que dois critérios de ordenação. Do mesmo modo, o manual do montador ORCA/M refere-se a um campo que indica a ordem dos bytes num campo numérico como NUMSEX, e o sistema Operativo Mac OS X refere o termo "byte sex" nos seus manuais de compiladores [2].

Ligações externas[editar | editar código-fonte]