1 pontos por GN⁺ 2024-10-19 | 1 comentários | Compartilhar no WhatsApp
  • O P3477R0 é uma proposta de mudança no padrão para que o tamanho do byte em C++ não fique a cargo do valor definido pela implementação chamado CHAR_BIT, mas seja fixado em exatamente 8 bits
  • As plataformas modernas já convergiram para o pressuposto de bytes de 8 bits, e GCC, LLVM e MSVC também definem os respectivos valores padrão ou macros como 8
  • O POSIX exige CHAR_BIT == 8 desde o POSIX.1-2001, e a adoção de representação inteira em complemento de dois no C++20 e no C23 segue na mesma direção
  • O suporte a bytes que não têm 8 bits deixa pequenas exceções em toda a linguagem, biblioteca e toolchain, criando um ônus de caso de borda que não condiz com o uso moderno real de C++
  • Arquiteturas excepcionais como PDP-10 ou alguns DSPs existem, mas a questão central é se o novo padrão C++ deve continuar mantendo essa complexidade para atendê-las

Objetivo de mudança do P3477R0

  • C++ herdou de C a macro CHAR_BIT, e atualmente esse valor é um valor definido pela implementação que indica o número de bits em um byte
  • O P3477R0 propõe alterar o padrão C++ para exigir oficialmente que um byte tenha 8 bits
  • A avaliação por trás disso é que, no início da computação, a flexibilidade de permitir diversos tamanhos de byte fazia sentido, mas o hardware moderno quase todo convergiu para bytes de 8 bits

Estado atual dos compiladores e plataformas

  • Os principais compiladores já tratam bytes de 8 bits como a realidade padrão
    • O GCC usa 8 como valor padrão, e não há casos entre os targets upstream que alterem esse padrão
    • O LLVM define __CHAR_BIT__ como 8
    • O MSVC define CHAR_BIT como 8
  • Entre os casos antigos de suporte do GCC, dsp16xx foi removido em 2004, e 1750a em 2002
  • Buscas na web mostram alguns ports externos do GCC em que BITS_PER_UNIT não é 8, mas a proposta considera que eles não parecem relevantes para C++ moderno

POSIX e a tendência na representação de inteiros

  • O POSIX exige as seguintes condições desde o POSIX.1-2001
    • Um byte tem exatamente 8 bits
    • CHAR_BIT é 8
    • SCHAR_MAX é 127, SCHAR_MIN é -128, UCHAR_MAX é 255
  • O POSIX explica que, como resultado da adição de int8_t, exige char de 8 bits e aritmética em complemento de dois
  • Desde o P0907r4, o C++20 oferece suporte apenas ao formato de armazenamento em complemento de dois, e o C23 segue a mesma direção
  • Como exemplos atuais de sistemas operacionais compatíveis com POSIX, são listados AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks e vz/OS

O custo deixado por bytes que não têm 8 bits

  • Software para bytes de 8 bits e software para bytes que não têm 8 bits não são compatíveis entre si, e o código C/C++ voltado a bytes que não têm 8 bits é visto, na prática, como algo próximo de dialetos incompatíveis de C e C++
  • O suporte a arquiteturas com bytes que não têm 8 bits deixa pequenas complexidades desnecessárias em várias partes da linguagem e da biblioteca
  • Compiladores e toolchains precisam continuar carregando casos de borda que não refletem o uso moderno
  • Novos programadores tendem a se confundir com essas características exóticas de C++
  • Alguns programadores experientes acabam gastando tempo com “portabilidade” para plataformas que não existem

Arquiteturas de exceção e alternativa de compromisso

  • A proposta também reconhece que processadores com bytes que não têm 8 bits ainda existem
  • A pergunta central é se esses processadores são relevantes para o C++ moderno e se seus usuários usarão novas versões de C++
  • Como alternativa de compromisso, também é apresentada a possibilidade de exigir CHAR_BIT % 8 == 0, mas isso só faz sentido se o comitê decidir continuar dando suporte a DSPs ou outros processadores em que CHAR_BIT não é 8, mas é múltiplo de 8
  • O PDP-10 é objeto da discussão, mas o PDP-11 é distinguido por usar bytes de 8 bits
  • Alguns DSPs tratam palavras de 24 ou 32 bits como “bytes”, e essas arquiteturas faziam sentido em uma época em que os tamanhos de palavra variavam e o conceito de byte não era padronizado

Direção das mudanças no texto do padrão

  • A ideia é alterar a definição de byte em intro.memory para declarar que o byte, a unidade básica de armazenamento do modelo de memória de C++, tem 8 bits
  • Em climits, a proposta sugere alterar o texto para que CHAR_BIT seja 8
  • Em cstdint, como um byte passa a ter 8 bits, tipos inteiros com largura especificada como int8_t, uint8_t e macros relacionadas deixam de ser opcionais
  • Entre os tipos que usam _N_, aqueles em que N não é 8, 16, 32 ou 64 continuam opcionais
  • A proposta inclui a remoção de quatro cláusulas de mandates relacionadas a CHAR_BIT == 8 em localization

Relação com o padrão C

  • A proposta examina se C++ deve continuar sendo relevante para arquiteturas com bytes que não têm 8 bits
  • O comitê de C pode chegar a uma conclusão diferente para a linguagem C
  • Embora o ideal seja que os dois comitês estejam alinhados, esta proposta deixa o grupo de contato WG14 e SG22 no papel de fornecer informações ao WG21

1 comentários

 
GN⁺ 2024-10-19
Opiniões no Hacker News
  • A série do JF “não dá para admitir que computadores reais já funcionam todos assim?” já teve um episódio sobre inteiros com sinal serem complemento de dois: "Signed Integers are Two’s Complement"

  • Em 1986, quando eu era estagiário, escrevi código C em um BBN C/70 que usava bytes de 10 bits; foi uma experiência horrível, e a própria existência desse tipo de máquina foi um acidente cósmico no sentido negativo

    • Já programei em um DECSYSTEM-20, mas o compilador C não tinha suporte oficial
      Ele usava palavras de 36 bits e bytes de 7 bits, e sobravam bits quando se empacotavam bytes em uma palavra. Ainda por cima me deram a tarefa de ler uma fita com dados binários em formato de 8 bits, então foi uma confusão
    • Programei a CPU do Intel Intellivision, uma máquina esquisita que usava decl de 10 bits e não era poderosa o bastante para rodar C
    • Trabalhei em uma máquina com bytes de 9 bits e instruções de 81 bits, e também em uma máquina com bytes de 6 bits, mas nenhuma das duas tinha compilador C
    • Em FPGAs hoje em dia, aritmética de 10 bits não é realmente rara e é usada até em produtos relativamente modernos
      Mas C de 10 bits é outra história
  • D deu um grande passo à frente ao definir o seguinte: byte tem 8 bits, short tem 16 bits, int tem 32 bits, long tem 64 bits, a aritmética é complemento de dois, e ponto flutuante é ponto flutuante IEEE
    Isso poupou uma quantidade enorme de tempo que era gasto tentando abstrair essas coisas e no fim fazendo errado, e milhões de pessoas respiraram aliviadas. O conjunto de caracteres também era Unicode, não EBCDIC nem RADIX-50

    • Zig é melhor: os tamanhos são explícitos, como u8/i8, u16/i16, u32/i32, u64/i64, e a aritmética também é escolhida explicitamente
      Overflow em + é comportamento incorreto, então interrompe em debug e releasesafe; +% é wrapping em complemento de dois; +| é aritmética saturada. @addWithOverflow() fornece uma tupla do tipo original e u1, e std.math.add() retorna erro em overflow. f16, f32, f64, f80 e f128 também são, respectivamente, tipos de ponto flutuante IEEE com esses comprimentos em bits. Não importa quantos bits tem um byte; em uma máquina com bytes de 12 bits, bastaria usar u12 e i12
    • D ter dado um grande passo à frente é exagero. Nomes de tipos com tamanho explícito, como u8, i32, são muito melhores em todos os aspectos
    • Se “um byte tem 8 bits”, qual é o tamanho de um bit?
    • Acho que isso é um pouco autoelogioso demais para o próprio sr. Bright, autor da linguagem D, dizer :)
    • Java também acertou nessa parte. Tratou unsigned errado, mas acertou ao padronizar a quantidade de bits dos tipos primitivos
      byte = 8 bits, short = 16, int = 32, long = 64, float = IEEE de 32 bits, double = IEEE de 64 bits
  • Ainda há pessoas que precisam lidar com DSPs: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
    Pessoalmente, estou documentando de brincadeira um console de fantasia de 12 bits não implementado, com “50% mais bits por byte que a concorrência!”, e incluí também invenções como “UTF-12”

    • Estou tentando confirmar quais alvos ainda são relevantes e se eles miram C++ moderno, ou se têm planos de fazer isso
      Perguntei por anos, mas não recebi respostas positivas; o único mencionado foi algo da TI, então acrescentei a informação ao rascunho atualizado: https://isocpp.org/files/papers/D3477R1.html
    • Acho que basta mirar C++23 ou anterior. Tenho alguns SHARCs, mas, se o comitê remover o suporte a CHAR_BIT=32 em alguma versão como C++30, não vou protestar aos prantos
    • O PDP-8 não usava bytes de 12 bits?
  • Fico me perguntando se C++ consegue descartar ou simplificar alguma coisa
    É uma pergunta sincera, e não acompanhei em detalhes. Ouvi dizer que rand() está quebrado e não dá para consertar, mas, até onde eu sabia, nem estava marcado para descontinuação. Esta proposta parece um teste do tipo “será que dá para remover também o suporte a uma solução para um problema que literalmente ninguém enfrenta?”

    • Inteiros com sinal não precisavam necessariamente ser complemento de dois; as três representações — sinal-magnitude, complemento de um e complemento de dois — eram todas válidas
      C e C++ modernos abandonaram isso e exigem complemento de dois. Aqui, a distinção “as if” também não importa na prática, e dá para aplicar a mesma lógica a CHAR_BIT, então claramente há precedente para esse tipo de mudança
    • Já removeram trígrafos (trigraphs), colocaram rand no caminho da descontinuação e ofereceram alternativas
      Além disso, há p2809 Trivial infinite loops are not Undefined Behavior, p1152 Deprecating volatile, p0907 Signed Integers are Two's Complement, p2723 Zero-initialize objects of automatic storage duration e p2186 Removing Garbage Collection Support. Então é possível mudar
    • A API de GC do C++11 foi removida no C++23, e isso é compreensível, já que ela não foi projetada levando em conta as necessidades das principais variantes que dão suporte a GC, como Unreal C++ e C++/CLI
      As especificações de exceção também foram removidas, embora haja quem queira ressuscitá-las para exceções por tipo de valor. auto_ptr também foi removido por causa de um design quebrado. No entanto, em termos de simplificação, isso não melhora muito, porque ainda é preciso conhecer o jeito antigo
    • Parece uma sátira da ideia de que não se deve quebrar a perfeição, mas sim acumular mais perfeição
      Seria algo como precisar de um novo símbolo em C++ que indique de forma confiável um byte de 8 bits sem quebrar compatibilidade. Por exemplo, daria para criar unsigned byte8, signed byte8 em complemento de dois e até byte8 com comportamento de sinal não definido. Para contadores, haveria unsigned decimal byte8 e signed decimal byte8, com faixa de valores limitada a 0~10 e -10~+10; para contadores que também levam em conta o custo dos bytes, centimal byte8, de 0~100 e -100~+100; um tipo “mais ou menos suficiente” para o campo age de um banco de dados; e, obviamente, também float byte8. É uma piada
    • Não sei por que rand() estaria quebrado. Ele gera valores que parecem aleatórios, e esse é o objetivo
      É claro que ele não gera números aleatórios criptograficamente seguros, e o mesmo vale para funções equivalentes em outras linguagens. Se a ideia é obter rapidamente inteiros razoavelmente aleatórios, rand() funciona bem o suficiente
  • Obrigado pelo interesse na proposta; com base no feedback recebido até agora, fiz um rascunho atualizado: https://isocpp.org/files/papers/D3477R1.html

    • Gosto do tom sarcástico da proposta
      Em especial, a frase “a questão não é se ainda existem arquiteturas em que um byte não tem 8 bits. Existem! A questão é se elas se importam com C++ moderno, e se C++ moderno se importa com elas” é marcante
  • Tenho sentimentos ambivalentes sobre esta proposta. Por um lado, ela está obviamente certa, e não há uso significativo para CHAR_BIT que não seja 8
    Por outro lado, também parece ceder a uma visão de mundo justa segundo a qual o mundo deveria fazer sentido e ser passível de raciocínio apenas com base em um modelo pessoal e excessivamente simplificado do interior de um computador. Essa abordagem leva você bem longe, mas acaba em um beco sem saída; no fim, é preciso admitir que não se sabe nada, e que o melhor argumento formal é dizer que você construiu um programa correto apenas sob a condição de que a documentação esteja correta. Esse é um grande salto conceitual e, pessoalmente, quanto mais tempo passei sem ser forçado a admiti-lo, mais difícil foi superar isso depois. Ainda assim, projetos de eletrônica física parecem estar ficando populares entre iniciantes hoje em dia, então espero que o novo padrão não seja “leia a documentação”, mas “leia a maldita folha de dados”

    • Mesmo assim, sempre que rodo scripts autoconf, vejo eles verificarem quantos bits há em um byte e salvarem isso em config.h, como se alguém realmente planejasse agir com base nesse valor
    • Uma linguagem amplamente usada acaba esbarrando no problema COBOL. Na maioria dos casos tudo bem, mas em algum sistema obrigado a ser atualizado, de repente um sistema de controle de tráfego pode parar ou um avião pode cair
      É preciso haver uma forma de verificar todo o código antigo durante a compilação para saber se esse macro já está sendo usado. Uma mudança incompatível desse tipo também corre o risco de dividir a linguagem. Também não está claro quão difícil seria testar se uma base de código existente usa o macro CHAR_BIT e se pode ser atualizada para um compilador novo. Surgem ainda dúvidas como quais bibliotecas seriam consideradas quebradas, ou se haveria problemas ao interagir com outro código compilado usando CHAR_BIT. Concordo que é contraintuitivo, mas seria melhor primeiro criar uma ferramenta de conversão, mostrar que ela é segura mesmo em casos extremos e só então fazer a transição
  • Gosto porque é uma proposta sem polêmica e, ao mesmo tempo, extremamente picante

  • Não vejo problema algum em impor int8_t == char == 8 bits, mas não tenho tanta certeza sobre espalhar o mal-entendido de que um byte tem 8 bits
    Um byte de 8 bits é chamado de octeto (octet). Ao mesmo tempo, desde C++17, byte já é uma espécie de “alias” de char: https://en.cppreference.com/w/cpp/types/byte

    • Comecei a mexer com computadores há 45 anos, e naquela época “byte” já era definido como uma quantidade de 8 bits
      Nos 45 anos seguintes, nunca vi “byte” ser usado com outro significado; então, se existe uma definição de “byte” que não seja 8 bits, é preciso uma fonte
    • As RFCs de redes usam continuamente o termo octet desde o começo
    • Não, um byte tem 8 bits
      Isso não é uma afirmação descritiva, é uma afirmação normativa
    • Pessoalmente, não gosto de int8 == signed char
      std::cout << (int8_t)32 << std::endl; obviamente deveria imprimir 32
  • Não tem relação com C++, mas gosto bastante da ideia de um micromputador retrô com bytes de 6 bits. Algo em que 24 bits seriam uma word
    Micromputadores normalmente lidam com um número pequeno de objetos e preferem arrays a ponteiros, então isso poderia economizar memória. VGA usava 6 bits por cor, dá para criar um alfabeto legível com uma matriz de bits 6x4, linguagens básicas como LISP ou Forth também caberiam em um alfabeto de 6 bits, e o System/360 original usava apenas endereços de 24 bits. Uma memória de 12 MiB em unidades de 6 bits endereçáveis de forma independente deveria ser suficiente para qualquer um. Se não bastar, dá para estender naturalmente o FAT-12 para FAT-24, ou usar ponteiros de 48 bits, tão úteis quanto ponteiros de 64 bits

    • Ou então usar bytes de 8 bits e words de 3 bytes. Ainda assim seriam 24 bits