TECH 으로 돌아가기
TECH HACKER NEWS 오늘 10분 읽기 25 READS

C의 정수 타입 크기가 '유동적'인 것은 실수가 아니었다

C의 정수 타입 크기가 '유동적'인 것은 실수가 아니었다
SOURCE IMAGE · HACKER NEWS

C를 처음 배우는 개발자가 반드시 통과하는 관문 중 하나가 sizeof 연산자다. 요즘 대부분의 머신에서 sizeof(int)를 출력하면 4가 나오기 때문에 '정수는 4바이트'라고 자연스럽게 받아들인다. 그러나 이는 우연히 현대 하드웨어가 그렇게 수렴했기 때문일 뿐, 언어가 보장하는 값이 아니다. 원문 저자는 자신이 처음 코딩을 배우던 386 머신에서는 같은 sizeof(int)가 2를 출력했다는 경험을 꺼낸다. char, short, int, long은 애초에 '몇 바이트를 차지한다'는 약속을 갖고 있지 않다. 파이썬, 자바스크립트, 자바 같은 언어에서 넘어온 학습자에게 이 사실은 종종 당혹스럽게 다가온다.

이 유동적인 크기를 두고 프로그래밍 커뮤니티에서는 흔히 'C의 설계 실수'라는 평가가 나온다. 실제로 int가 어떤 머신에서는 16비트, 다른 머신에서는 32비트이고, long은 리눅스에서 64비트지만 64비트 윈도우에서는 32비트인 탓에 수십 년간 이식성 버그가 양산됐다. 거의 모든 CPU가 8·16·32·64비트 연산을 처리하는 오늘날의 시각에서 보면 '왜 처음부터 크기를 고정하지 않았나'라는 의문은 합리적이다. 하지만 원문의 핵심 주장은, 이것이 1970년대의 설계 결정을 2020년대의 조건으로 재단하는 오류라는 것이다.

컴퓨터가 무엇인지 합의되지 않았던 시대

C가 설계되던 1960~70년대에는 '컴퓨터'라는 단어의 의미조차 통일돼 있지 않았다. 워드 크기는 12, 18, 36, 60비트 등으로 제각각이었고, 문자 집합도 6비트, 7비트 ASCII, 36비트 머신의 9비트 바이트, IBM 메인프레임의 EBCDIC가 혼재했다. 음수 표현 방식도 2의 보수, 1의 보수, 부호-크기 방식이 공존했다. 어떤 머신은 바이트 단위로 주소를 지정할 수 있었지만, 어떤 머신은 워드 전체만 주소 지정이 가능해 '문자를 가리키는 포인터'가 워드 주소에 오프셋을 더한 형태여야 했다. 이렇게 파편화된 하드웨어 위에서 하나의 언어가 효율적으로 동작하려면, 타입 크기를 하드웨어에 맡기는 것이 오히려 자연스러운 선택이었다.

C의 정수 철학은 그 조상을 보면 이해가 쉽다. 마틴 리처즈의 BCPL(1967)과 켄 톰프슨의 B(1969년경)는 타입이 없는 언어였고, 값이라고는 '머신 워드' 하나뿐이었다. 변수는 워드를 담았고, 적용하는 연산자에 따라 정수·주소·비트패턴으로 해석됐다. 워드 단위로 주소를 지정하는 머신에서는 이 모델이 우아하고 효율적이었다. 그러나 벨 연구소가 바이트 주소 지정 방식에 16비트 워드를 쓰고 부동소수점 하드웨어를 갖추게 될 PDP-11을 도입하면서 문제가 드러났다. 데니스 리치는 'The Development of the C Language'(1993)에서 B의 '모든 것은 워드' 모델이 이 머신에 얼마나 맞지 않았는지 기술한다. 문자 처리가 서툴렀고 포인터는 워드와 바이트 주소 사이에서 스케일링돼야 했으며 부동소수점 값은 워드에 담기지 않았다.

C의 타입 시스템은 이 불일치를 해결하기 위해 만들어졌다. char는 바이트를, int는 BCPL 워드의 정신 곧 '그 머신이 가장 자연스럽게 다루는 정수'를 이어받았다. C11 표준(§6.2.5)에 남아 있는 '실행 환경 아키텍처가 권장하는 자연스러운 크기'라는 문구가 바로 그 의도를 담은 의도적 설계 선언이다. int는 결코 '32비트'를 뜻한 적이 없다.

크기를 고정했다면 치렀을 비용

이 설계의 실효성은 1977~78년 리치와 스티브 존슨이 유닉스와 C를 PDP-11과 상당히 다른 32비트 머신 Interdata 8/32로 포팅하면서 검증됐다('Portability of C Programs and the UNIX System', 1978). 비슷한 시기 존슨의 Portable C Compiler(pcc)는 새 아키텍처로의 재타기팅을 실용적으로 만들었다. 커니핸과 리치의 'The C Programming Language' 초판(1978)에는 당시 C가 이미 돌아가던 네 머신의 타입 크기 표가 실려 있는데, 16비트·32비트·36비트 int와 8비트·9비트 char가 함께 등장한다. 유동적 크기는 처음부터 있었고 실제로 작동했다.

만약 C가 int를 정확히 32비트, 2의 보수, 오버플로 시 래핑으로 강제했다면 비용이 컸다. PDP-11에서 32비트 값을 더하려면 하위 워드에 ADD, 상위 워드에 ADC(캐리 포함 덧셈)를 해야 해서 모든 int 연산이 두 배로 늘어났을 것이다. 반대로 36비트 머신에서는 정확한 32비트 래핑을 유지하려고 거의 모든 연산 뒤에 마스킹 명령을 추가하고 워드마다 4비트를 낭비해야 했다. int를 36비트로 두면 이런 비용이 전혀 들지 않았다. 1의 보수 머신에서 2의 보수 래핑을 강제했다면 모든 부호 연산이 소프트웨어 에뮬레이션을 요구했을 것이다. C는 세 가지 부호 표현을 모두 허용하고 부호 있는 오버플로를 미정의 동작으로 남겨 이를 피했다. char도 정확히 8비트가 아니라 최소 8비트(CHAR_BIT >= 8)만 요구해, 36비트 머신에서는 9비트 char 네 개가 워드에 딱 들어맞도록 했다.

최소 범위 보장이라는 실무적 타협

다만 크기가 완전히 자유로운 것은 아니다. 표준은 int의 최소 범위를 -32767~32767로 정해 최소 16비트를 보장한다. 크기는 머신에 적응하되 프로그램이 기댈 수 있는 하한선을 둔 것이다. 1989년 ANSI 표준화는 이 철학을 명문화했다. 정확한 크기 대신 로 노출되는 보장된 최소 범위를 주는 방식이다. 이식성 있는 C 프로그램은 '정확히 N비트인 타입이 무엇인가'가 아니라 '내 범위를 담을 수 있는 가장 작은 타입은 무엇인가'를 묻는다. 값이 10만까지 필요하면 어디서나 그 값을 담도록 보장된 long을 쓰고, ±32767 안이면 int로 충분하며 모든 머신에서 가장 빠른 선택이 된다.

이 규율을 현대 저장소에서 잘 보여주는 예가 Lua다. Lua는 64비트 서버부터 마이크로컨트롤러, 오래되고 특이한 툴체인까지 두루 돌아가며, 저자들이 'Clean C'라 부르는 ANSI C(C89)의 공통 부분집합으로 작성됐다. 오랫동안 에 의존하지 않고 'unsigned int가 최소 32비트 범위를 담는가'를 물었다. l_uint32 타입은 이름과 달리 최소 32비트만 필요로 하며, 16비트 int 머신에서는 표준이 32비트 이상을 보장하는 long으로 떨어진다. Lua는 uint8_t 대신 unsigned char를 쓰는데, 이는 바이트가 9비트나 16비트인 머신에서도 옳다. 그런 머신에서는 uint8_t가 아예 존재하지 않기 때문이다. string.pack/unpack은 바이트를 8비트로 가정하지 않고 & MC와 우측 시프트로 정수를 다루며 엔디언을 코드에서 명시적으로 처리한다.

물론 유동적 크기가 공짜였다고 미화할 수는 없다. int 크기 차이가 낳은 이식성 버그는 실재했고, 그래서 C99가 를 도입했다. 그러나 여기서도 C는 원래 철학을 지켰다. int8_t·uint32_t 같은 정확한 폭 타입은 실제로 그 폭의 타입이 존재할 때만 제공되는 선택 사항이고, 의무적으로 제공되는 것은 int_least32_t('최소 32비트인 가장 작은 타입')와 int_fast32_t('최소 32비트인 가장 빠른 타입')다. 위원회는 여전히 모든 머신이 8비트 바이트를 갖는다고 가정하기를 거부했다. 한편 1990년대 이후 설계된 언어들은 아키텍처 전쟁이 끝난 세계에서 출발해 8비트 바이트, 바이트 주소 지정, 2의 보수를 전제로 삼을 수 있었다. 흥미롭게도 이들조차 '플랫폼 크기의 자연스러운 정수' 개념은 버리지 않았다. 러스트의 isize/usize, Go의 int, 스위프트의 Int, Zig의 usize가 그것이다. 다만 그 의미가 '머신의 자연스러운 워드'에서 '포인터 크기'로 좁혀졌을 뿐이다. C 자신도 하드웨어 수렴을 따라, 1의 보수·부호-크기 머신이 극히 드물어진 2020년대에 이르러서야 C23에서 부호 있는 정수의 2의 보수 표현을 요구하게 됐다. 표준이 현실이 안정된 뒤에 그 현실을 따라간 셈이며, 이는 실수의 교정이 아니라 트레이드오프의 재조정이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://pikuma.com/blog/c-integer-sizes-not-a-mistake
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...