1P by GN⁺ | ★ favorite | 댓글 1개
  • P3477R0은 C++의 바이트 크기를 CHAR_BIT라는 구현 정의 값에 맡기지 않고 정확히 8비트로 고정하자는 표준 변경안임
  • 현대 플랫폼은 이미 8비트 바이트를 전제로 수렴했으며, GCC·LLVM·MSVC도 관련 기본값이나 매크로를 8로 두고 있음
  • POSIX는 POSIX.1-2001부터 CHAR_BIT == 8을 요구했고, C++20과 C23의 2의 보수 정수 표현 채택 흐름도 같은 방향에 있음
  • 비 8비트 바이트 지원은 언어·라이브러리·툴체인 전반에 작은 예외를 남기며, 실제 현대 C++ 사용과 맞지 않는 엣지 케이스 부담을 만든다고 봄
  • PDP-10이나 일부 DSP 같은 예외적 아키텍처는 존재하지만, 새 C++ 표준이 계속 그 대상을 위해 복잡성을 유지해야 하는지가 핵심 쟁점임

P3477R0의 변경 목표

  • C++은 C의 CHAR_BIT 매크로를 가져오며, 현재 이 값은 바이트의 비트 수를 나타내는 구현 정의 값
  • P3477R0은 C++ 표준이 바이트를 8비트로 공식 요구하도록 바꾸자고 제안함
  • 컴퓨팅 초창기에는 다양한 바이트 크기를 허용하는 유연성이 의미 있었지만, 현대 하드웨어는 거의 모두 8비트 바이트를 전제로 수렴했다는 판단이 깔려 있음

컴파일러와 플랫폼의 현재 상태

  • 주요 컴파일러는 이미 8비트 바이트를 기본 현실로 다룸
    • GCC는 기본값을 8로 두며, 업스트림 타깃 중 이 기본값을 바꾸는 사례가 없음
    • LLVM__CHAR_BIT__8로 설정함
    • MSVCCHAR_BIT8로 정의함
  • GCC의 과거 지원 사례 중 dsp16xx는 2004년에, 1750a는 2002년에 제거됨
  • 웹 검색으로는 BITS_PER_UNIT이 8이 아닌 일부 GCC 외부 포트가 보이지만, 현대 C++과 관련 있어 보이지 않는다고 봄

POSIX와 정수 표현의 흐름

  • POSIX는 POSIX.1-2001부터 다음 조건을 요구함
    • 바이트는 정확히 8비트
    • CHAR_BIT8
    • SCHAR_MAX127, SCHAR_MIN-128, UCHAR_MAX255
  • POSIX는 int8_t 추가의 결과로 8비트 char와 2의 보수 산술을 요구한다고 설명함
  • C++20은 P0907r4 이후 2의 보수 저장 방식만 지원하며, C23도 같은 방향을 따름
  • 현재 POSIX 준수 운영체제 예시로 AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks, vz/OS가 나열됨

비 8비트 바이트가 남기는 비용

  • 8비트 바이트용 소프트웨어와 비 8비트 바이트용 소프트웨어는 서로 호환되지 않으며, 비 8비트 바이트 대상 C/C++ 코드는 사실상 C와 C++의 호환되지 않는 방언에 가깝다고 봄
  • 비 8비트 바이트 아키텍처 지원은 언어와 라이브러리 여러 부분에 작지만 불필요한 복잡성을 남김
  • 컴파일러와 툴체인은 현대 사용을 반영하지 않는 엣지 케이스를 계속 떠안아야 함
  • 새 프로그래머는 C++의 이런 이국적인 특성 때문에 혼란을 겪기 쉬움
  • 일부 숙련된 프로그래머는 존재하지 않는 플랫폼을 위한 “이식성”에 시간을 쓰게 된다고 봄

예외 아키텍처와 절충안

  • 비 8비트 바이트 프로세서가 여전히 존재한다는 점은 제안도 인정함
  • 핵심 질문은 그런 프로세서가 현대 C++ 과 관련 있는지, 그리고 그 프로세서 사용자가 새 C++ 버전을 사용할지 여부임
  • 절충안으로 CHAR_BIT % 8 == 0을 요구하는 방안도 제시되지만, 이는 위원회가 CHAR_BIT가 8은 아니지만 8의 배수인 DSP나 기타 프로세서를 계속 지원하기로 할 때만 의미가 있음
  • PDP-10은 논의 대상이지만, PDP-11은 8비트 바이트를 사용한다고 구분함
  • 일부 DSP는 24비트 또는 32비트 워드를 “바이트”처럼 다루며, 이런 아키텍처는 단어 크기가 다양하고 바이트 개념이 표준화되지 않았던 시대에는 타당했음

표준 문구 변경 방향

  • intro.memory의 바이트 정의를 바꿔 C++ 메모리 모델의 기본 저장 단위인 바이트가 8비트라고 명시하려 함
  • climits에서는 CHAR_BIT8로 두는 방향의 문구 변경을 제안함
  • cstdint에서는 바이트가 8비트이므로 int8_t, uint8_t 등 폭이 지정된 정수 타입과 관련 매크로가 선택 사항이 아니게 됨
  • _N_을 쓰는 타입 중 N8, 16, 32, 64가 아닌 경우에는 계속 선택 사항으로 둠
  • localization 안의 CHAR_BIT == 8 관련 4개 mandates 절은 제거하는 변경을 포함함

C 표준과의 관계

  • 제안은 C++이 비 8비트 바이트 아키텍처와 계속 관련 있어야 하는지를 검토함
  • C 위원회는 C 언어에 대해 다른 결론에 도달할 수 있음
  • 두 위원회가 정렬되는 것이 이상적이지만, 이 제안은 WG14와 SG22 연락 그룹이 WG21에 정보를 제공하는 방향으로 둠

댓글과 토론

Hacker News 의견들
  • JF의 “실제 컴퓨터는 다 이렇게 동작한다고 인정할 수 있지 않나?” 시리즈에는 이미 부호 있는 정수는 2의 보수라는 편이 있었음: "Signed Integers are Two’s Complement"

  • 1986년 인턴 때 10비트 바이트를 쓰는 BBN C/70에서 C 코드를 작성했는데 끔찍한 경험이었고, 애초에 그런 기계가 존재한 것 자체가 부정적인 의미의 우주적 사고였음

    • DECSYSTEM-20에서 코드를 짠 적이 있는데, C 컴파일러는 공식 지원이 아니었음
      36비트 워드7비트 바이트를 썼고, 바이트를 워드에 채워 넣으면 비트가 남았음. 거기에 8비트 형식의 바이너리 데이터가 담긴 테이프를 읽는 일을 맡았으니 난리가 났음
    • Intel Intellivision CPU를 프로그래밍했는데 10비트 decl을 쓰는 괴상한 기계였고, C를 돌릴 만큼 강력하지 않았음
    • 9비트 바이트81비트 명령어를 쓰는 기계, 그리고 6비트 바이트를 쓰는 기계에서 일해봤지만 둘 다 C 컴파일러는 없었음
    • 요즘 FPGA에서는 10비트 산술이 실제로 드물지 않고 비교적 현대적인 제품에도 쓰임
      하지만 10비트 C라면 얘기가 달라짐
  • D는 다음을 정하면서 크게 전진했음: 바이트는 8비트, short는 16비트, int는 32비트, long은 64비트, 산술은 2의 보수, 부동소수점은 IEEE 부동소수점
    이런 걸 추상화하려다 결국 틀리는 데 쓰던 엄청난 시간이 절약됐고, 수백만 명이 안도했음. 문자 집합도 EBCDIC이나 RADIX-50이 아니라 Unicode였음

    • Zig는 더 나음: u8/i8, u16/i16, u32/i32, u64/i64처럼 크기가 명시되고, 산술도 명시적으로 선택함
      +의 오버플로는 잘못된 동작이라 디버그와 releasesafe에서 중단되고, +%2의 보수 래핑, +|는 포화 산술임. @addWithOverflow()는 원래 타입과 u1의 튜플을 주고, std.math.add()는 오버플로에서 오류를 반환함. f16, f32, f64, f80, f128도 각각 해당 비트 길이의 IEEE 부동소수점 타입임. 바이트 길이가 몇인지는 중요하지 않고, 12비트 바이트 기계라면 u12i12를 쓰면 됨
    • D가 큰 전진을 했다는 건 과장임. 명시적 크기 이름인 u8, i32 같은 타입명이 모든 면에서 훨씬 낫다
    • “바이트는 8비트”라면 비트는 얼마나 큰가?
    • D 언어 작성자인 Bright 씨가 직접 하기엔 조금 자화자찬 아닌가 싶음 :)
    • Java도 이 부분은 맞게 했음. unsigned는 잘못 처리했지만 기본 타입 비트 수 표준화는 제대로 했음
      byte = 8 bits, short = 16, int = 32, long = 64, float = 32 bit IEEE, double = 64 bit IEEE
  • 아직도 DSP를 다뤄야 하는 사람들은 있음: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
    개인적으로는 “경쟁사보다 바이트당 비트가 50% 더 많다!”는 미구현 12비트 판타지 콘솔을 장난 삼아 문서화하는 중이고, “UTF-12” 같은 발명품도 넣어둠

    • 아직 관련 있는 대상이 무엇이고, 그들이 현대 C++를 겨냥하는지 혹은 그럴 계획이 있는지 확인하려고 하고 있음
      몇 년째 물어봤지만 긍정적인 답은 못 받았고, 언급된 건 TI 정도뿐이라 갱신 초안에 정보를 추가했음: https://isocpp.org/files/papers/D3477R1.html
    • 그냥 C++23 이하를 대상으로 하면 되지 않나 싶음. SHARC 몇 대를 갖고 있지만, 위원회가 C++30 같은 버전에서 CHAR_BIT=32 지원을 빼도 울며 항의하진 않을 것임
    • PDP-8은 12비트 바이트를 쓰지 않았나?
  • C++가 뭔가를 폐기하거나 단순화할 수는 있는지 궁금함
    솔직한 질문이고 자세히 따라가진 않았음. rand()는 망가졌고 고칠 수 없다고 들었는데, 마지막으로 알기로는 아직 폐기 예정도 아니었음. 이 제안이 “문자 그대로 아무도 겪지 않는 문제의 해법 지원도 없앨 수 있는가?”라는 시험처럼 보임

    • 부호 있는 정수는 꼭 2의 보수일 필요가 없었고, 부호-크기, 1의 보수, 2의 보수라는 세 표현이 모두 유효했음
      현대 C와 C++는 이를 버리고 2의 보수를 요구함. 여기서는 “as if” 구분도 실질적으로 중요하지 않고, CHAR_BIT에도 같은 식으로 적용할 수 있으니 이런 변화의 선례는 분명히 있음
    • 이미 삼중자(trigraph)를 제거했고 rand도 폐기 예정으로 만들었으며 대안도 제공함
      그 외에도 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가 있음. 그러니 바꾸는 건 가능함
    • C++11의 GC API는 C++23에서 제거됐고, GC를 지원하는 주요 변종인 Unreal C++와 C++/CLI의 요구를 고려해 설계되지 않았다는 점에서 이해할 만함
      예외 명세도 제거됐지만, 값 타입 예외를 위해 되살리길 원하는 사람들도 있음. auto_ptr도 망가진 설계 때문에 제거됨. 다만 단순화 측면에서는 예전 방식도 여전히 알아야 하므로 별로 나아지지 않음
    • 완벽을 깨지 말고 더 많은 완벽을 누적해야 한다는 풍자처럼 보임
      호환성을 깨지 않으면서 8비트 바이트를 안정적으로 가리키는 새 C++ 기호가 필요하다는 식임. 예를 들어 unsigned byte8, 2의 보수 signed byte8, 부호 동작이 정의되지 않은 byte8까지 만들 수 있음. 회계사를 위해 값 범위를 0~10, -10~+10으로 제한하는 unsigned decimal byte8signed decimal byte8, 바이트 비용까지 따지는 회계사를 위해 0~100, -100~+100의 centimal byte8, 데이터베이스의 age 필드에 대충 충분한 타입, 그리고 당연히 float byte8도 추가하자는 농담임
    • rand()가 왜 망가졌는지 모르겠음. 무작위처럼 보이는 값을 만들어내고, 그게 목적임
      암호학적으로 안전한 난수를 만들지 않는 건 당연하고 다른 언어의 동등한 함수도 마찬가지임. 빠르게 계산되는 적당히 무작위인 정수라면 rand()는 충분히 잘 동작함
  • 제안에 관심 가져줘서 고맙고, 지금까지 받은 피드백을 바탕으로 갱신 초안을 만들었음: https://isocpp.org/files/papers/D3477R1.html

    • 제안서의 비꼬는 문체가 좋음
      특히 “문제는 바이트가 8비트가 아닌 아키텍처가 아직 있느냐가 아니다. 있다! 문제는 그들이 현대 C++를 신경 쓰는지, 그리고 현대 C++가 그들을 신경 쓰는지다”라는 문장이 인상적임
  • 이 제안에는 양가감정이 듦. 한편으로는 명백히 맞고, CHAR_BIT가 8이 아닌 데 의미 있는 용도는 없음
    다른 한편으로는 컴퓨터 내부에 대한 개인적이고 지나치게 단순화된 모델만으로 세상이 이치에 맞고 추론 가능해야 한다는 공정한 세계관에 굴복하는 느낌도 있음. 이런 접근은 꽤 멀리 데려가지만 결국 막다른 길이고, 마지막에는 아무것도 모른다는 점과 문서가 맞다는 조건에서만 올바른 프로그램을 구성했다는 형식적 논증이 최선임을 인정해야 함. 이건 큰 지적 도약이고, 개인적으로는 그걸 인정하도록 강제되지 않고 오래 갈수록 나중에 뛰어넘기 더 어려웠음. 그래도 요즘 초보자들 사이에서 물리 전자 프로젝트가 인기를 얻는 듯하니, “문서를 읽어라” 대신 “망할 데이터시트를 읽어라”가 새 표준이 되길 바람

    • 그런데도 autoconf 스크립트를 돌릴 때마다 바이트의 비트 수를 검사하고 config.h에 저장하는 걸 보게 됨. 마치 누군가 그 값에 따라 실제로 행동할 계획이라도 있는 것처럼 보임
    • 널리 쓰이는 언어라면 결국 COBOL 문제에 부딪힘. 대부분의 경우엔 괜찮지만, 업데이트가 강제된 어떤 시스템에서 갑자기 교통관제 시스템이 멈추거나 비행기가 추락할 수도 있음
      이전 코드 전체를 컴파일 과정에서 검사해 이 매크로가 이미 쓰이는지 확인할 방법이 필요함. 이런 깨지는 변경은 언어를 갈라놓을 위험도 있음. 기존 코드베이스가 CHAR_BIT 매크로를 쓰는지, 새 컴파일러로 갱신 가능한지 테스트하는 난도도 명확하지 않음. 어떤 라이브러리가 깨지는 것으로 간주될지, CHAR_BIT를 사용해 컴파일된 다른 코드와 상호작용할 때도 문제가 생길지 같은 의문도 떠오름. 직관에 어긋난다는 데는 동의하지만, 먼저 변환 도구를 만들고 극단적인 경우에도 안전하다는 걸 보여준 뒤 전환하는 편이 좋겠음
  • 논쟁의 여지가 없으면서도 엄청나게 매운 제안이라 마음에 듦

  • int8_t == char == 8 bits를 강제하는 건 전적으로 괜찮지만, 바이트가 8비트라는 오해를 퍼뜨리는 건 확신이 없음
    8비트 바이트는 옥텟(octet)이라고 부름. 동시에 C++17부터 byte는 이미 char의 “별칭” 같은 존재이기도 함: https://en.cppreference.com/w/cpp/types/byte

    • 컴퓨터를 처음 접한 게 45년 전인데, 그때도 “byte”는 8비트 양으로 정의됐음
      그 뒤 45년 동안 “byte”가 다른 뜻으로 쓰이는 걸 본 적이 없으니, 8비트가 아닌 “byte” 정의가 있다면 출처가 필요함
    • 네트워킹 RFC들은 처음부터 octet이라는 표현을 계속 써왔음
    • 아니, 바이트는 8비트임
      이건 묘사적 진술이 아니라 규범적 진술
    • 개인적으로는 int8 == signed char가 싫음
      std::cout << (int8_t)32 << std::endl;는 당연히 32를 출력해야 함
  • C++와는 상관없지만, 6비트 바이트 레트로 마이크로컴퓨터라는 아이디어가 꽤 마음에 듦. 24비트가 워드가 되는 식임
    마이크로컴퓨터는 보통 적은 수의 객체를 다루고 포인터보다 배열을 선호하니 메모리를 아낄 수 있음. VGA는 색상당 6비트였고, 6x4 비트 행렬로 읽을 만한 알파벳을 만들 수 있고, 기본 LISP나 Forth 언어도 6비트 알파벳에 넣을 수 있으며, 원래 System/360은 24비트 주소만 썼음. 독립적으로 주소 지정 가능한 6비트 단위의 12MiB 메모리면 누구에게나 충분해야 함. 부족하면 FAT-12를 FAT-24로 자연스럽게 확장하거나, 64비트 포인터만큼 쓸모 있는 48비트 포인터를 쓰면 됨

    • 아니면 8비트 바이트3바이트 워드를 쓰면 됨. 그래도 24비트임