Por que as bibliotecas padrão não são primitivas de linguagem de programação? [fechadas]


30

Eu estava pensando por que existem (em todas as linguagens de programação que aprendi, como C ++, Java, Python) bibliotecas padrão como stdlib, em vez de ter "funções" semelhantes, sendo uma primitiva da própria linguagem.


4
O que você quer dizer com "por que o compilador não pode simplesmente converter uma chamada de função em um conjunto de instruções"? Isso é aproximadamente o que o compilador faz, biblioteca padrão ou não (Ok, Python apenas parcialmente e Java para JVM bytecode; conceito semelhante). As bibliotecas padrão realmente não têm nada a ver com a compilação de código -> instruções.
Delioth 29/01

25
@ Delioth Acho que Simone está perguntando por que tudo na biblioteca padrão da linguagem $ LANG não é uma construção / função primitiva dessa linguagem. Eu diria que é uma pergunta razoável para quem é muito novo em linguagens de programação :)
Andres F.

33
A biblioteca padrão geralmente preenche a lacuna entre uma linguagem de programação funcional e uma útil que as pessoas usarão.
Telastyn 29/01

6
Uma parte significativa da biblioteca padrão do Python é realmente escrita em C e já compilada.
ElmoVanKielmo 29/01

11
Como forma de contraste, na maioria das implementações do BASIC, tudo faz parte da linguagem e não há bibliotecas nem suporte para elas (exceto, em várias implementações, a capacidade de acessar rotinas de linguagem de máquina).
Euro Micelli

Respostas:


32

Permitam-me expandir um pouco a boa resposta de @ Vincent (+1) :

Por que o compilador não pode simplesmente converter uma chamada de função em um conjunto de instruções?

Pode e faz isso por pelo menos dois mecanismos:

  • inline uma chamada de função - durante a tradução, o compilador pode substituir uma chamada de código fonte por sua implementação diretamente inline, em vez de fazer uma chamada real para a função. Ainda assim, a função precisa ter uma implementação definida em algum lugar e que possa estar na biblioteca padrão.

  • função intrínseca - intrínseca são funções das quais o compilador foi informado sem necessariamente encontrar a função em uma biblioteca. Eles geralmente são reservados para recursos de hardware que não são praticamente acessíveis de nenhuma outra maneira, sendo tão simples que até a sobrecarga de uma chamada para a função de biblioteca de linguagem assembly é considerada alta. (O compilador geralmente só pode incorporar automaticamente o código-fonte embutido em seu idioma, mas não as funções de assembly, que é onde entra o mecanismo intrínseco.)

Ainda assim, algumas vezes, a melhor opção é o compilador converter uma chamada de função no idioma de origem em uma chamada de função no código da máquina. Recursão, métodos virtuais e tamanho absoluto são alguns dos motivos pelos quais o embutimento nem sempre é possível / prático. (Outro motivo é a intenção da compilação, como compilação separada (módulos de objeto), unidades de carregamento separadas (por exemplo, DLLs)).

Também não há nenhuma vantagem real em tornar a maioria das funções de biblioteca padrão intrísicas (que codificariam muito mais conhecimento no compilador sem vantagem real); portanto, uma chamada de código de máquina novamente é frequentemente mais apropriada.

C é uma linguagem notável que possivelmente omitiu outras declarações de linguagem explícita em favor das funções padrão da biblioteca. Embora as bibliotecas existissem, essa linguagem mudou para fazer mais trabalho a partir das funções padrão da biblioteca e menos como declarações explícitas na gramática da linguagem. O IO em outros idiomas, por exemplo, recebeu frequentemente sua própria sintaxe na forma de várias instruções, enquanto a gramática C não define nenhuma instrução de IO, simplesmente adiando para sua biblioteca padrão para fornecer isso, tudo acessível por meio de chamadas de função, que o compilador já sabe como fazer.


3
Boa resposta. Deve-se acrescentar algumas palavras por que essa decisão em C foi tomada: se bem me lembro, a principal razão foi porque facilitou a criação de compiladores C para muitas arquiteturas de hardware diferentes.
Doc Brown

10
@DocBrown Em 1975, havia exemplos suficientes na área de desenvolvimento da linguagem de programação (ALGOL-68, alguém?), Que mostravam que as tentativas de incorporar tudo na linguagem diretamente levavam a lentidões consideráveis ​​ao aprimorar as especificações da linguagem e na produção de implementações de linguagem.
Joker_vD 29/01

5
Um exemplo semelhante é o que o Python fez com print: No 2.x, era uma declaração , com sua própria gramática especial, mas no 3.x, tornou-se apenas mais uma chamada de função. Veja PEP 3105 para a explicação oficial.
dan04 29/01

11
@ DocBrown, a portabilidade quase certamente não foi um motivo. Quando o Unix e o C foram criados, eles foram projetados e construídos para exatamente uma máquina, um PDP-7 sobressalente, pois Ken Thompson se perguntava quais conceitos poderiam ser recuperados do projeto Multics que falhou. C também foi criado por um motivo: ter uma linguagem de alto nível para (re) implementar o Unix. Eles são basicamente um experimento em design de software, não uma tentativa séria de um sistema operacional e linguagem comercial de várias plataformas. Veja bell-labs.com/usr/dmr/www/chist.html por exemplo.
Euro Micelli

@EuroMicelli: Não vejo contradição. E sua referência contém muitos detalhes sobre quando a portabilidade se tornou importante, na verdade foi nos primeiros anos do desenvolvimento do C e Unix. Só posso adivinhar aqui, mas se os inventores de C não tivessem mantido intencionalmente a linguagem pequena, acho que seria bastante improvável que eles pudessem portá-la tão rapidamente e com sucesso para muitas arquiteturas diferentes.
Doc Brown

70

Isso é simplesmente para manter o idioma em si o mais simples possível. Você precisa distinguir entre um recurso da linguagem, como um tipo de loop ou maneiras de passar parâmetros para funções e assim por diante, e a funcionalidade comum que a maioria dos aplicativos precisa.

Bibliotecas são funções que podem ser úteis para muitos programadores e, portanto, são criadas como códigos reutilizáveis ​​que podem ser compartilhados. As bibliotecas padrão são projetadas para serem funções muito comuns que os programadores normalmente precisam. Dessa forma, a linguagem de programação é imediatamente útil para uma ampla gama de programadores. As bibliotecas podem ser atualizadas e estendidas sem alterar os principais recursos do próprio idioma.


3
Nem sempre. PHPcomo exemplo, dificilmente faz diferença entre suas vastas funções linguísticas e a própria linguagem.
Vahid Amiri 30/01

15
Eu não tomaria o PHP como exemplo de uma linguagem simples
DrBreakalot 30/01

3
@DrBreakalot PHP é uma linguagem extremamente simples. Isso não quer dizer que ele tenha um design consistente, mas isso é outra questão.
Lightness Races com Monica

19
@LightnessRacesinOrbit Eu não chamaria o PHP de "simples": possui um sistema de objetos com base em classe, um conjunto separado de 'valores primitivos', funções independentes, fechamentos de primeira classe criados no sistema de objetos, um mecanismo de namespace, vários noções chamados "estático", declarações, bem como expressões, include, requiree require_once, se / for / while (programação estruturada), exceções, um sistema separado de 'valores de erro', complicado regras de digitação fracos, complicado regras de precedência, e assim por diante . Compare isso com a simplicidade de, digamos, Smalltalk, Scheme, Prolog, Forth, etc.;)
Warbo

3
O principal motivo, sugerido, mas não explicitamente declarado nesta resposta, é que, mantendo o idioma o mais simples possível, é muito mais fácil implementar em outras plataformas. Como as bibliotecas padrão geralmente são escritas no próprio idioma , elas podem ser portadas trivialmente.
BlueRaja - Danny Pflughoeft 30/01

34

Além do que as outras respostas já disseram, colocar funções padrão em uma biblioteca é uma separação de preocupações :

  • O trabalho do compilador é analisar o idioma e gerar código para ele. Não é tarefa do compilador conter qualquer coisa que já possa ser escrita nesse idioma e fornecida como uma biblioteca.

  • É o trabalho da biblioteca padrão (a que está sempre disponível implicitamente) para fornecer as principais funcionalidades necessárias a praticamente todos os programas. Não é tarefa da biblioteca padrão conter todas as funções que podem ser úteis.

  • É o trabalho das bibliotecas padrão opcionais fornecer funcionalidades auxiliares que muitos programas podem dispensar, mas que ainda são bastante básicas e também essenciais para muitos aplicativos que justificam o envio com ambientes padrão. Não é tarefa dessas bibliotecas opcionais conter todo o código reutilizável que já foi escrito.

  • O trabalho das bibliotecas de usuários é fornecer coleções de funções reutilizáveis ​​úteis. Não é tarefa das bibliotecas de usuários conter todo o código que já foi escrito.

  • O trabalho do código fonte de um aplicativo é fornecer os bits restantes de código que são realmente relevantes apenas para esse aplicativo.

Se você deseja um software de tamanho único, obtém algo incrivelmente complexo. Você precisa modularizar para reduzir a complexidade a níveis gerenciáveis. E você precisa modularizar para permitir implementações parciais :

  • A biblioteca de encadeamento é inútil no controlador incorporado de núcleo único. Permitir que a implementação da linguagem desse controlador incorporado não inclua a pthreadbiblioteca é a coisa certa a fazer.

  • A biblioteca de matemática é inútil no microcontrolador que nem possui uma FPU. Mais uma vez, não ser forçado a fornecer funções como sin()facilita muito a vida dos implementadores da sua linguagem para esse microcontrolador.

  • Até a biblioteca padrão principal é inútil quando você está programando um kernel. Você não pode implementar write()sem um syscall no kernel e não pode implementar printf()sem write(). Como programador de kernel, é seu trabalho fornecer o write()syscall, você não pode apenas esperar que ele esteja lá.

Uma linguagem que não permite tais omissões nas bibliotecas padrão simplesmente não é adequada para muitas tarefas . Se você deseja que seu idioma seja utilizável de maneira flexível em ambientes incomuns, ele deve ser flexível em quais bibliotecas padrão estão incluídas. Quanto mais o seu idioma depende de bibliotecas padrão, mais suposições são feitas no ambiente de execução e, portanto, restringe seu uso a ambientes que fornecem esses pré-requisitos.

Obviamente, linguagens de alto nível, como python e java, podem fazer muitas suposições em seu ambiente. E eles tendem a incluir muitas e muitas coisas em suas bibliotecas padrão. Linguagens de nível inferior como C fornecem muito menos em suas bibliotecas padrão e mantêm a biblioteca padrão principal muito menor. É por isso que você encontra um compilador C funcionando para praticamente qualquer arquitetura, mas pode não ser capaz de executar nenhum script python nele.


16

Um grande motivo pelo qual os compiladores e as bibliotecas padrão são separados é porque eles servem a dois propósitos diferentes (mesmo que ambos sejam definidos pela mesma especificação de idioma): o compilador converte código de nível superior em instruções da máquina, e a biblioteca padrão fornece pré-testes implementações da funcionalidade comumente necessária. Os escritores de compiladores valorizam a modularidade, assim como outros desenvolvedores de software. De fato, alguns dos primeiros compiladores C dividiram ainda mais o compilador em programas separados para pré-processamento, compilação e vinculação.

Essa modularidade oferece várias vantagens:

  • Minimiza a quantidade de trabalho necessária ao oferecer suporte a uma nova plataforma de hardware, pois a maioria do código de biblioteca padrão é independente de hardware e pode ser reutilizada.
  • Uma implementação de biblioteca padrão pode ser otimizada de diferentes maneiras (velocidade, espaço, uso de recursos etc.). Muitos sistemas de computação antigos tinham apenas um compilador disponível e, com uma biblioteca padrão separada, os desenvolvedores podiam trocar implementações para atender às suas necessidades.
  • A funcionalidade da biblioteca padrão nem precisa existir. Ao escrever código C bare-metal, por exemplo, você tem um compilador completo, mas a maior parte da funcionalidade da biblioteca padrão não existe e algumas coisas como E / S de arquivo nem são possíveis. Se o compilador fosse obrigado a implementar essa funcionalidade, não seria possível ter um compilador C em conformidade com os padrões em algumas das plataformas em que você mais precisa.
  • Nos primeiros sistemas, os compiladores eram frequentemente desenvolvidos pela empresa que projetava o hardware. As bibliotecas padrão eram frequentemente fornecidas pelo fornecedor do SO, uma vez que frequentemente exigiam acesso a funcionalidades (como chamadas do sistema) específicas para essa plataforma de software. Era impraticável para um gravador de compilador suportar todas as diferentes combinações de hardware e software (costumava haver muito mais variedade na arquitetura de hardware e na plataforma de software).
  • Em linguagens de alto nível, uma biblioteca padrão pode ser implementada como uma biblioteca carregada dinamicamente. Uma implementação de biblioteca padrão pode ser usada por vários compiladores e / ou linguagens de programação.

Historicamente falando (pelo menos da perspectiva de C), as versões originais de pré-padronização da linguagem não tinham uma biblioteca padrão. Fornecedores de SO e terceiros costumavam fornecer bibliotecas cheias de funcionalidades usadas com freqüência, mas implementações diferentes incluíam coisas diferentes e eram amplamente incompatíveis entre si. Quando C foi padronizado, eles definiram uma "biblioteca padrão" na tentativa de harmonizar essas implementações díspares e melhorar a portabilidade. A biblioteca padrão C desenvolvida separadamente da linguagem, como as bibliotecas Boost para C ++, mas posteriormente foram integradas às especificações da linguagem.


6

Resposta adicional em maiúsculas: Gerenciamento de propriedade intelectual

Um exemplo notável é a implementação do Math.Pow (duplo, duplo) no .NET Framework que foi comprado pela Microsoft à Intel e permanece não revelado, mesmo que o framework tenha sido de código aberto. (Para ser preciso, no caso acima, é uma chamada interna, e não uma biblioteca, mas a idéia é válida.) Uma biblioteca separada da própria linguagem (teoricamente também um subconjunto de bibliotecas padrão) pode dar aos patrocinadores da linguagem mais flexibilidade para desenhar os linha entre o que manter transparente e o que deve permanecer não divulgado (devido a contratos com terceiros ou outros motivos relacionados à PI).


Isso é confuso. A página para a qual você vincula Math.Pownão menciona nenhuma compra ou nada sobre a Intel e fala sobre pessoas lendo o código-fonte da implementação da função.
Lightness Races com Monica

@LightnessRacesinOrbit - hm, ainda posso vê-lo lá (ao procurar por "intel"). Você também pode encontrar a referência ao código-fonte recente (nos comentários mais recentes) e também uma implementação alternativa (na segunda resposta) que está disponível ao público, mas a complexidade e a ineficiência comentada dão uma dica de por que a implementação original ainda não foi divulgada. A implementação verdadeiramente eficiente pode exigir um conhecimento profundo de muitos detalhes no nível da CPU que não estão necessariamente disponíveis em domínio público.
miroxlav 30/01

5

Erros e depuração.

Bugs: Todo software possui bugs, sua biblioteca padrão possui bugs e seu compilador possui bugs. Como usuário da linguagem, é muito mais fácil encontrar e solucionar esses bugs quando eles estão na biblioteca padrão e não no compilador.

Depuração: É muito mais fácil para mim ver um rastreamento de pilha de uma biblioteca padrão e me dar uma idéia do que pode estar errado. Porque esse rastreamento de pilha tem código que eu entendo. É claro que você pode aprofundar e também rastrear suas funções intrínsecas, mas é muito mais fácil se estiver em um idioma que você usa o tempo todo, dia após dia.


5

Esta é uma excelente pergunta!

Estado da arte

O padrão C ++, por exemplo, nunca especifica o que deve ser implementado no compilador ou na biblioteca padrão: apenas se refere à implementação . Por exemplo, símbolos reservados são definidos pelo compilador (como intrínseco) e pela biblioteca padrão, de forma intercambiável.

No entanto, todas as implementações de C ++ que eu conheço terão o número mínimo possível de intrínsecas fornecidas pelo compilador e o máximo possível pela biblioteca padrão.

Portanto, embora seja tecnicamente viável definir a biblioteca padrão como funcionalidade intrínseca no compilador, ela raramente é usada na prática.

Por quê?

Vamos considerar a idéia de mover algumas funcionalidades da biblioteca padrão para o compilador.

Vantagens:

  • Melhores diagnósticos: os intrínsecos podem ser especiais.
  • Melhor desempenho: os intrínsecos podem ser especiais.

Desvantagens:

  • Massa aumentada do compilador: cada caso especial adiciona complexidade ao compilador; a complexidade aumenta os custos de manutenção e a probabilidade de erros.
  • Iteração mais lenta: alterar a implementação da funcionalidade requer a alteração do próprio compilador, dificultando a criação de apenas uma pequena biblioteca (fora std) para ser experimentada.
  • Maior barra de entrada: quanto mais caro / mais difícil é mudar alguma coisa, menor a probabilidade de as pessoas entrarem.

Isso significa que mover algo para o compilador é caro , agora e no futuro e, portanto, requer um caso sólido. Para algumas partes da funcionalidade, é necessário (elas não podem ser escritas como código regular), mas mesmo assim vale a pena extrair partes mínimas e genéricas para ir para o compilador e criar sobre elas na biblioteca padrão.


5

Como designer de linguagem, gostaria de repetir algumas das outras respostas aqui, mas fornecê-lo através dos olhos de alguém que está construindo um idioma.

Uma API não é concluída quando você adiciona tudo o que pode. Uma API é concluída quando você termina de tirar tudo o que pode.

Uma linguagem de programação deve ser especificada usando alguma linguagem. Você precisa transmitir o significado de qualquer programa escrito em seu idioma. Essa linguagem é muito difícil de escrever e ainda mais difícil de escrever bem. Em geral, tende a ser uma forma de inglês muito precisa e bem estruturada, usada para transmitir significado não ao computador, mas a outros desenvolvedores, especialmente aqueles que escrevem compiladores ou intérpretes para o seu idioma. Aqui está um exemplo da especificação do C ++ 11, [intro.multithread / 14]:

A sequência visível de efeitos colaterais em um objeto atômico M, em relação a um cálculo de valor B de M, é uma subseqüência máxima contígua de efeitos colaterais na ordem de modificação de M, em que o primeiro efeito colateral é visível em relação a B , e para todos os efeitos colaterais, não é o caso de B acontecer antes dele. O valor de um objeto atômico M, conforme determinado pela avaliação B, deve ser o valor armazenado por alguma operação na sequência visível de M em relação a B. [Nota: Pode ser demonstrado que a sequência visível dos efeitos colaterais de um valor A computação é única, considerando os requisitos de coerência abaixo. - end note]

Blek! Qualquer um que tenha mergulhado no entendimento de como o C ++ 11 lida com o multithreading pode entender por que o texto deve ser tão opaco, mas isso não perdoa o fato de que é ... bem ... tão opaco!

Compare isso com a definição de std::shared_ptr<T>::reset, na seção de biblioteca do padrão:

template <class Y> void reset(Y* p);

Efeitos: Equivalente ashared_ptr(p).swap(*this)

Então qual a diferença? Na parte de definição da linguagem, os escritores não podem assumir que o leitor entende as primitivas da linguagem. Tudo deve ser especificado com cuidado na prosa inglesa. Quando chegamos à parte de definição da biblioteca, podemos usar a linguagem para especificar o comportamento. Isso geralmente é muito mais fácil!

Em princípio, pode-se ter uma construção suave a partir de primitivas no início do documento de especificação, definindo o que consideraríamos "recursos de biblioteca padrão", sem precisar traçar uma linha entre "primitivas de linguagem" e recursos de "biblioteca padrão". Na prática, essa linha é extremamente valiosa para desenhar, pois permite que você escreva algumas das partes mais complexas da linguagem (como aquelas que precisam implementar algoritmos) usando uma linguagem projetada para expressá-las.

E de fato vemos algumas linhas borradas:

  • Em Java, java.lang.ref.Reference<T> pode ser subclassificado pelas classes de biblioteca padrão e porque os comportamentos de são tão profundamente entrelaçados com a especificação da linguagem Java que eles precisavam colocar algumas restrições na parte desse processo implementada como classes de "biblioteca padrão".java.lang.ref.WeakReference<T> java.lang.ref.SoftReference<T>java.lang.ref.PhantomReference<T>Reference
  • Em C #, há uma classe, System.Delegate, que encapsula o conceito de delegados. Apesar do nome, não é um delegado. Também é uma classe abstrata (não pode ser instanciada) da qual você não pode criar classes derivadas. Somente o sistema pode fazê-lo através de recursos gravados na especificação de idioma.

2

Isto é uma adição às respostas existentes (e é muito longo para um comentário).

Há pelo menos dois outros motivos para uma biblioteca padrão:

Barreira à Entrada

Se um recurso de idioma específico estiver em uma função de biblioteca e eu quiser saber como ele funciona, eu posso ler a fonte dessa função. Se eu quiser enviar uma solicitação de relatório de bug / patch / pull, geralmente não é muito difícil codificar uma correção e casos de teste. Se estiver no compilador, tenho que ser capaz de cavar os internos. Mesmo que esteja no mesmo idioma (e deveria ser, qualquer compilador que se preze deve ser auto-hospedado), o código do compilador não é nada parecido com o código do aplicativo. Pode levar uma eternidade para encontrar os arquivos corretos.

Você está se afastando de muitos colaboradores em potencial se seguir esse caminho.

Carregamento de código quente

Muitos idiomas oferecem esse recurso em um grau ou outro, mas seria muito complicado recarregar o código que está sendo recarregado a quente. Se o SL for separado do tempo de execução, ele poderá ser recarregado.


3
"qualquer compilador que se preze deve ser auto-hospedado" - de maneira alguma. Seria inútil ter versões do LLVM escritas em C, C ++, Objective-C, Swift, Fortran e assim por diante para compilar todas essas linguagens.
gnasher729 29/01

@ gnasher729 não é um caso especial (junto com outros destinos multilíngues, como o CLR)?
Jared Smith

@JaredSmith, eu diria que agora é o caso geral, nada especial. Ninguém mais escreve "um compilador" como um aplicativo monolítico. Eles produzem sistemas de compiladores . A maior parte da funcionalidade do compilador completo é completamente independente do idioma específico que está sendo compilado, e grande parte da parte dependente do idioma pode ser feita fornecendo dados diferentes que definem a gramática do idioma, não escrevendo códigos diferentes para cada idioma que você quer compilar.
alephzero 30/01

2

Esta é uma pergunta interessante, mas já existem muitas boas respostas, portanto não tentarei uma completa.

No entanto, duas coisas que eu acho que não receberam atenção suficiente:

Primeiro é que a coisa toda não é super clara. É um pouco de espectro exatamente porque existem razões para fazer as coisas de maneira diferente. Como exemplo, os compiladores geralmente conhecem as bibliotecas padrão e suas funções. Exemplo do exemplo: A função "Hello World" de C - printf - é a melhor que consigo pensar. É uma função de biblioteca, tem que ser, pois depende muito da plataforma. Mas seu comportamento (definido pela implementação) precisa ser conhecido pelo compilador para alertar o programador sobre más invocações. Isso não é particularmente interessante, mas foi visto como um bom compromisso. Aliás, essa é a resposta real para a maioria das perguntas "por que esse design": muito comprometimento e "parecia uma boa idéia na época". Nem sempre o "este era o caminho claro para fazê-lo" ou "

A segunda é que ela permite que a biblioteca padrão não seja todo esse padrão. Existem muitas situações em que uma linguagem é desejável, mas as bibliotecas padrão que geralmente as acompanham não são práticas e desejáveis. Esse é geralmente o caso de linguagens de programação de sistemas como C, em plataformas não padrão. Por exemplo, se você possui um sistema sem um sistema operacional ou um agendador: não terá o encadeamento.

Com um modelo de biblioteca padrão (e com suporte para threading), isso pode ser tratado de maneira limpa: o compilador é praticamente o mesmo, você pode reutilizar os bits das bibliotecas que se aplicam e qualquer coisa que não seja possível remover. Se isso for inserido no compilador, as coisas começam a ficar confusas.

Por exemplo:

  • Você não pode ser um compilador compatível.

  • Como você indicaria seu desvio do padrão. Observe que geralmente existe alguma forma de sintaxe de importação / inclusão, que pode causar falha, por exemplo, a importação de pythons ou a inclusão de C que facilmente aponta para o problema se houver algo faltando no modelo de biblioteca padrão.

Problemas semelhantes também se aplicam se você deseja ajustar ou ampliar a funcionalidade da 'biblioteca'. Isso é muito mais comum do que você imagina. Apenas para ficar com o threading: janelas, linux e algumas unidades de processamento de rede exóticas, todos funcionam de maneira bem diferente. Embora os bits do linux / windows possam ser razoavelmente estáticos e possam usar uma API idêntica, o material da NPU mudará com o dia da semana e a API com ele. Os compiladores se desviavam rapidamente à medida que as pessoas decidiam quais bits eles precisavam suportar / poderiam fazer com rapidez se não houvesse maneira de dividir esse tipo de coisa.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.