1 puntos por GN⁺ 2024-10-19 | 1 comentarios | Compartir por WhatsApp
  • P3477R0 es una propuesta de cambio al estándar para que el tamaño del byte en C++ no quede en manos del valor definido por la implementación CHAR_BIT, sino que se fije en exactamente 8 bits
  • Las plataformas modernas ya convergieron bajo el supuesto de bytes de 8 bits, y GCC, LLVM y MSVC también tienen el valor predeterminado o las macros relacionadas en 8
  • POSIX exige CHAR_BIT == 8 desde POSIX.1-2001, y la adopción de la representación de enteros en complemento a dos en C++20 y C23 también va en la misma dirección
  • Se considera que admitir bytes que no sean de 8 bits deja pequeñas excepciones en todo el lenguaje, las bibliotecas y el toolchain, y genera una carga de casos borde que no coincide con el uso real del C++ moderno
  • Aunque existen arquitecturas excepcionales como PDP-10 o algunos DSP, el punto central del debate es si el nuevo estándar de C++ debe seguir manteniendo complejidad para esos destinos

Objetivos de cambio de P3477R0

  • C++ adopta la macro CHAR_BIT de C, y actualmente este valor es un valor definido por la implementación que indica la cantidad de bits de un byte
  • P3477R0 propone cambiar el estándar de C++ para exigir oficialmente que un byte tenga 8 bits
  • La idea de fondo es que, aunque en los inicios de la computación tenía sentido permitir flexibilidad en distintos tamaños de byte, el hardware moderno casi en su totalidad convergió sobre el supuesto de bytes de 8 bits

Estado actual de compiladores y plataformas

  • Los principales compiladores ya tratan los bytes de 8 bits como la realidad predeterminada
    • GCC usa 8 como valor predeterminado, y no hay casos entre los targets upstream que cambien ese valor
    • LLVM configura __CHAR_BIT__ en 8
    • MSVC define CHAR_BIT como 8
  • Entre los casos históricos de soporte en GCC, dsp16xx se eliminó en 2004 y 1750a en 2002
  • En búsquedas web aparecen algunos ports externos de GCC donde BITS_PER_UNIT no es 8, pero se considera que no parecen relevantes para el C++ moderno

POSIX y la tendencia en la representación de enteros

  • POSIX exige las siguientes condiciones desde POSIX.1-2001
    • Un byte tiene exactamente 8 bits
    • CHAR_BIT es 8
    • SCHAR_MAX es 127, SCHAR_MIN es -128, UCHAR_MAX es 255
  • POSIX explica que, como resultado de agregar int8_t, exige char de 8 bits y aritmética en complemento a dos
  • Desde P0907r4, C++20 solo admite almacenamiento en complemento a dos, y C23 sigue la misma dirección
  • Como ejemplos actuales de sistemas operativos compatibles con POSIX se enumeran AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks y vz/OS

El costo que dejan los bytes que no son de 8 bits

  • El software para bytes de 8 bits y el software para bytes que no son de 8 bits no son compatibles entre sí, y se considera que el código C/C++ orientado a bytes que no son de 8 bits es, en la práctica, más cercano a dialectos incompatibles de C y C++
  • El soporte para arquitecturas con bytes que no son de 8 bits deja una complejidad pequeña pero innecesaria en muchas partes del lenguaje y las bibliotecas
  • Los compiladores y toolchains deben seguir cargando con casos borde que no reflejan el uso moderno
  • Los programadores nuevos pueden confundirse fácilmente por estas características exóticas de C++
  • Se considera que algunos programadores experimentados terminan dedicando tiempo a una “portabilidad” para plataformas inexistentes

Arquitecturas excepcionales y compromisos

  • La propuesta reconoce que todavía existen procesadores con bytes que no son de 8 bits
  • La pregunta central es si esos procesadores son relevantes para el C++ moderno y si sus usuarios adoptarán nuevas versiones de C++
  • Como compromiso, también se plantea exigir CHAR_BIT % 8 == 0, pero eso solo tendría sentido si el comité decide seguir admitiendo DSP u otros procesadores donde CHAR_BIT no sea 8, pero sí un múltiplo de 8
  • PDP-10 se incluye en la discusión, pero se distingue que PDP-11 usa bytes de 8 bits
  • Algunos DSP tratan palabras de 24 o 32 bits como “bytes”, y ese tipo de arquitecturas tenía sentido en una época en la que los tamaños de palabra variaban y el concepto de byte no estaba estandarizado

Dirección de los cambios en la redacción del estándar

  • Se busca cambiar la definición de byte en intro.memory para especificar que el byte, la unidad básica de almacenamiento del modelo de memoria de C++, tiene 8 bits
  • En climits, se propone cambiar la redacción para dejar CHAR_BIT en 8
  • En cstdint, como el byte tiene 8 bits, los tipos enteros de ancho especificado como int8_t, uint8_t y sus macros relacionadas dejarían de ser opcionales
  • Los tipos que usan _N_ donde N no sea 8, 16, 32 o 64 seguirían siendo opcionales
  • Se incluye un cambio para eliminar las 4 cláusulas mandates relacionadas con CHAR_BIT == 8 dentro de localization

Relación con el estándar C

  • La propuesta examina si C++ debe seguir siendo relevante para arquitecturas con bytes que no son de 8 bits
  • El comité de C podría llegar a una conclusión distinta para el lenguaje C
  • Aunque lo ideal sería que ambos comités se alinearan, esta propuesta deja en manos de WG14 y del grupo de enlace SG22 informar a WG21

1 comentarios

 
GN⁺ 2024-10-19
Opiniones en Hacker News
  • En la serie de JF de “¿no podemos simplemente aceptar que las computadoras reales funcionan así?”, ya había una entrega sobre los enteros con signo son complemento a dos: "Signed Integers are Two’s Complement"

  • Durante una pasantía en 1986 escribí código C en una BBN C/70 que usaba bytes de 10 bits; fue una experiencia terrible, y que una máquina así haya existido ya era en sí un accidente cósmico en el mal sentido

    • Llegué a escribir código en un DECSYSTEM-20, pero el compilador de C no tenía soporte oficial
      Usaba palabras de 36 bits y bytes de 7 bits, y al llenar una palabra con bytes sobraban bits. Encima me tocó leer una cinta con datos binarios en formato de 8 bits, así que fue un caos total
    • Programé el CPU de la Intel Intellivision, que era una máquina rarísima que usaba decl de 10 bits, y no era lo bastante potente como para correr C
    • He trabajado en una máquina con bytes de 9 bits e instrucciones de 81 bits, y también en una con bytes de 6 bits, pero ninguna de las dos tenía compilador de C
    • Hoy en día, en FPGA la aritmética de 10 bits en realidad no es tan rara y se usa incluso en productos relativamente modernos
      Pero un C de 10 bits ya es otra historia
  • D dio un gran paso adelante al fijar lo siguiente: byte de 8 bits, short de 16 bits, int de 32 bits, long de 64 bits, aritmética en complemento a dos y punto flotante en IEEE
    Eso ahorró una enorme cantidad de tiempo que antes se desperdiciaba intentando abstraer estas cosas para al final equivocarse, y millones de personas respiraron aliviadas. El conjunto de caracteres también era Unicode, no EBCDIC ni RADIX-50

    • Zig es todavía mejor: el tamaño queda explícito con u8/i8, u16/i16, u32/i32, u64/i64, y la aritmética también se elige de forma explícita
      El overflow de + es comportamiento inválido y aborta en debug y releasesafe, +% hace wrapping en complemento a dos, y +| es aritmética saturada. @addWithOverflow() devuelve una tupla del tipo original y u1, y std.math.add() devuelve un error si hay overflow. f16, f32, f64, f80 y f128 también son tipos IEEE de punto flotante con esas longitudes de bits respectivas. No importa cuántos bits tenga un byte; si fuera una máquina con bytes de 12 bits, bastaría usar u12 e i12
    • Decir que D dio un gran paso adelante es exagerado. Los nombres de tipo con tamaño explícito como u8, i32 son mucho mejores en todos los aspectos
    • Si “un byte son 8 bits”, entonces ¿qué tan grande es un bit?
    • ¿No será un poco de autobombo para que lo diga el propio autor de D, el señor Bright? :)
    • Java también hizo bien esta parte. Manejó mal unsigned, pero sí estandarizó correctamente la cantidad de bits de los tipos básicos
      byte = 8 bits, short = 16, int = 32, long = 64, float = 32 bit IEEE, double = 64 bit IEEE
  • Todavía hay gente que tiene que lidiar con DSP: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
    Personalmente, por diversión estoy documentando una consola de fantasía de 12 bits aún no implementada con el gancho de “¡50% más bits por byte que la competencia!”, e incluso le puse inventos como “UTF-12”

    • Sigo intentando verificar cuáles objetivos siguen siendo relevantes y si apuntan a C++ moderno o planean hacerlo
      Llevo años preguntando y no he recibido ninguna respuesta positiva; aparte de TI, casi no se ha mencionado a nadie, así que agregué información al borrador actualizado: https://isocpp.org/files/papers/D3477R1.html
    • Yo diría que simplemente apunten a C++23 o anterior. Tengo algunas SHARC, pero si el comité deja fuera el soporte para CHAR_BIT=32 en una versión como C++30, no me voy a poner a llorar ni a protestar
    • ¿No usaba el PDP-8 bytes de 12 bits?
  • Me pregunto si C++ realmente puede deprecar o simplificar algo
    Es una pregunta sincera y no he seguido el tema en detalle. Tengo entendido que rand() está roto y no se puede arreglar, pero hasta donde sabía ni siquiera estaba en camino a quedar deprecado. Esta propuesta parece una prueba de “¿se puede eliminar incluso el soporte para resolver un problema que literalmente nadie enfrenta?”

    • Los enteros con signo no tenían por qué ser necesariamente en complemento a dos, y las tres representaciones —signo y magnitud, complemento a uno y complemento a dos— eran válidas
      C y C++ modernos dejaron eso atrás y exigen complemento a dos. Aquí la distinción de “as if” tampoco importa realmente, y lo mismo podría aplicarse a CHAR_BIT, así que claramente hay precedentes de este tipo de cambio
    • Ya eliminaron los trigraphs, rand ya está en camino a quedar deprecado y además existen alternativas
      También están 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, p2186 Removing Garbage Collection Support. Así que sí se puede cambiar
    • La API de GC de C++11 fue eliminada en C++23, lo cual es entendible porque no fue diseñada considerando las necesidades de variantes importantes con GC como Unreal C++ y C++/CLI
      Las especificaciones de excepciones también se eliminaron, aunque hay quienes quieren revivirlas para excepciones de tipos por valor. auto_ptr también se eliminó por su diseño roto. Pero desde el punto de vista de la simplificación no mejora mucho, porque igual hay que conocer la forma anterior
    • Parece una sátira de la idea de no romper ninguna perfección y en cambio ir acumulando todavía más perfección
      Algo así como que hace falta un nuevo símbolo en C++ para señalar de forma estable bytes de 8 bits sin romper compatibilidad. Por ejemplo, unsigned byte8, signed byte8 en complemento a dos y hasta byte8 con comportamiento de signo indefinido. También se podría agregar unsigned decimal byte8 y signed decimal byte8, con rango de 0~10 y -10~+10 para contadores, centimal byte8 con 0~100 y -100~+100 para contadores que incluso se preocupan por el costo del byte, algún tipo más o menos suficiente para el campo age de una base de datos y, claro, también float byte8
    • No entiendo por qué rand() está roto
      Genera valores que parecen aleatorios, y ese es el objetivo. Obviamente no produce números aleatorios criptográficamente seguros, y las funciones equivalentes en otros lenguajes tampoco. Si lo que se quiere es un entero razonablemente aleatorio que se calcule rápido, rand() funciona suficientemente bien
  • Gracias por interesarte en la propuesta; con base en los comentarios recibidos hasta ahora preparé un borrador actualizado: https://isocpp.org/files/papers/D3477R1.html

    • Me gusta el tono sarcástico de la propuesta
      En particular, me quedó grabada la frase “el problema no es si todavía existen arquitecturas donde un byte no son 8 bits. ¡Existen! El problema es si a ellas les importa el C++ moderno, y si al C++ moderno le importan ellas”
  • Tengo sentimientos encontrados con esta propuesta. Por un lado, evidentemente tiene razón y CHAR_BIT distinto de 8 no tiene un uso significativo
    Por otro lado, también se siente como rendirse ante una visión justa del mundo en la que el mundo debe ser sensato y razonable solo con base en un modelo personal y excesivamente simplificado del interior de una computadora. Ese enfoque te puede llevar bastante lejos, pero al final es un callejón sin salida, y tarde o temprano hay que aceptar que no sabes nada y que, bajo la condición de que la documentación sea correcta, lo mejor a lo que puedes aspirar es a un argumento formal de que construiste un programa correcto. Es un salto intelectual grande, y personalmente cuanto más tiempo pasé sin verme obligado a reconocerlo, más difícil fue después superarlo. Aun así, como parece que los proyectos físicos con electrónica se están volviendo populares entre principiantes, ojalá el nuevo estándar pase de “lee la documentación” a “lee el maldito datasheet”

    • Y aun así, cada vez que corro scripts de autoconf veo que revisan la cantidad de bits por byte y la guardan en config.h, como si alguien realmente pensara actuar en función de ese valor
    • En un lenguaje ampliamente usado, al final uno choca con el problema COBOL. En la mayoría de los casos no pasa nada, pero en algún sistema donde se fuerce una actualización, de pronto podría detenerse un sistema de control de tráfico o caer un avión
      Haría falta alguna forma de revisar todo el código previo durante la compilación para verificar si ese macro ya se usa. Este tipo de cambio incompatible también corre el riesgo de partir el lenguaje. Tampoco está clara la dificultad de probar si una base de código existente usa el macro CHAR_BIT y si puede actualizarse a un compilador nuevo. También surgen dudas sobre qué bibliotecas se considerarían rotas, o si habría problemas al interactuar con otro código compilado usando CHAR_BIT. Estoy de acuerdo en que resulta contraintuitivo, pero preferiría que primero existiera una herramienta de migración y que se demostrara que la transición es segura incluso en casos extremos
  • Me gusta porque es una propuesta tremendamente picante y al mismo tiempo indiscutible

  • Me parece totalmente bien forzar int8_t == char == 8 bits, pero no estoy convencido de difundir la idea equivocada de que un byte son 8 bits
    Un byte de 8 bits se llama octeto. Al mismo tiempo, desde C++17 byte ya es algo parecido a un “alias” de char: https://en.cppreference.com/w/cpp/types/byte

    • La primera vez que usé una computadora fue hace 45 años, y ya entonces “byte” se definía como una cantidad de 8 bits
      En los 45 años posteriores nunca he visto que “byte” se use con otro significado, así que si existe una definición de “byte” que no sea de 8 bits, necesito ver la fuente
    • Los RFC de redes siempre han seguido usando el término octet desde el principio
    • No, un byte son 8 bits
      Esto no es una afirmación descriptiva sino una afirmación normativa
    • Personalmente no me gusta que int8 == signed char
      std::cout << (int8_t)32 << std::endl; obviamente debería imprimir 32
  • No tiene que ver con C++, pero me gusta bastante la idea de una retro microcomputadora de bytes de 6 bits. Sería algo donde 24 bits forman una palabra
    Las microcomputadoras normalmente manejan pocos objetos y prefieren arreglos sobre punteros, así que se puede ahorrar memoria. VGA usaba 6 bits por color, se puede crear un alfabeto legible con una matriz de 6x4 bits, incluso lenguajes básicos como LISP o Forth caben en un alfabeto de 6 bits, y el System/360 original solo usaba direcciones de 24 bits. Una memoria de 12 MiB con unidades direccionables independientes de 6 bits debería ser suficiente para cualquiera. Si no alcanza, se podría extender FAT-12 de forma natural a FAT-24, o usar punteros de 48 bits, tan útiles como los punteros de 64 bits

    • O si no, usar bytes de 8 bits y palabras de 3 bytes. Igual siguen siendo 24 bits