C++ 제안: 바이트는 정확히 8비트
(open-std.org)- 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의 과거 지원 사례 중
dsp16xx는 2004년에,1750a는 2002년에 제거됨 - 웹 검색으로는
BITS_PER_UNIT이 8이 아닌 일부 GCC 외부 포트가 보이지만, 현대 C++과 관련 있어 보이지 않는다고 봄
POSIX와 정수 표현의 흐름
- POSIX는 POSIX.1-2001부터 다음 조건을 요구함
- 바이트는 정확히 8비트
CHAR_BIT는8SCHAR_MAX는127,SCHAR_MIN은-128,UCHAR_MAX는255
- 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_BIT를8로 두는 방향의 문구 변경을 제안함cstdint에서는 바이트가 8비트이므로int8_t,uint8_t등 폭이 지정된 정수 타입과 관련 매크로가 선택 사항이 아니게 됨_N_을 쓰는 타입 중N이8,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"
- 다음은 부동소수점이 항상 IEEE 부동소수점이라고 명시하는 차례일지도 모름
다만 그러면 이 Linux 커널 고전 코드가 낡아버릴 수 있음: https://github.com/torvalds/linux/blob/master/include/math-emu/double.h#L29
- 다음은 부동소수점이 항상 IEEE 부동소수점이라고 명시하는 차례일지도 모름
-
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라면 얘기가 달라짐
- DECSYSTEM-20에서 코드를 짠 적이 있는데, 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비트 바이트 기계라면u12와i12를 쓰면 됨 - 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
- Zig는 더 나음:
-
아직도 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++를 겨냥하는지 혹은 그럴 계획이 있는지 확인하려고 하고 있음
-
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 byte8와signed decimal byte8, 바이트 비용까지 따지는 회계사를 위해 0~100, -100~+100의centimal byte8, 데이터베이스의age필드에 대충 충분한 타입, 그리고 당연히float byte8도 추가하자는 농담임 rand()가 왜 망가졌는지 모르겠음. 무작위처럼 보이는 값을 만들어내고, 그게 목적임
암호학적으로 안전한 난수를 만들지 않는 건 당연하고 다른 언어의 동등한 함수도 마찬가지임. 빠르게 계산되는 적당히 무작위인 정수라면rand()는 충분히 잘 동작함
- 부호 있는 정수는 꼭 2의 보수일 필요가 없었고, 부호-크기, 1의 보수, 2의 보수라는 세 표현이 모두 유효했음
-
제안에 관심 가져줘서 고맙고, 지금까지 받은 피드백을 바탕으로 갱신 초안을 만들었음: 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를 출력해야 함
- 컴퓨터를 처음 접한 게 45년 전인데, 그때도 “byte”는 8비트 양으로 정의됐음
-
C++와는 상관없지만, 6비트 바이트 레트로 마이크로컴퓨터라는 아이디어가 꽤 마음에 듦. 24비트가 워드가 되는 식임
마이크로컴퓨터는 보통 적은 수의 객체를 다루고 포인터보다 배열을 선호하니 메모리를 아낄 수 있음. VGA는 색상당 6비트였고, 6x4 비트 행렬로 읽을 만한 알파벳을 만들 수 있고, 기본 LISP나 Forth 언어도 6비트 알파벳에 넣을 수 있으며, 원래 System/360은 24비트 주소만 썼음. 독립적으로 주소 지정 가능한 6비트 단위의 12MiB 메모리면 누구에게나 충분해야 함. 부족하면 FAT-12를 FAT-24로 자연스럽게 확장하거나, 64비트 포인터만큼 쓸모 있는 48비트 포인터를 쓰면 됨- 아니면 8비트 바이트와 3바이트 워드를 쓰면 됨. 그래도 24비트임