8장. CP949 — 통합 완성형의 확장

만화로 보는 요약 — 먼저 읽어보세요
CP949는 EUC-KR을 깨지 않고 빈 코드 공간에 나머지 한글을 채워 호환성을 지켰지만, 일부 두 번째 바이트가 ASCII와 겹쳐 바이트 단위 처리의 함정을 남겼다.
면접 실전 질문: ① CP949가 EUC-KR과 호환되는 이유는? ② CP949가 해결한 한글 문제는? ③ 두 번째 바이트의 ASCII 충돌은 왜 위험한가?
배경 — 표준을 갈아엎지 않고 늘리기
7장은 뼈아픈 상태로 끝났어요. 완성형(EUC-KR)이 국가 표준으로 자리 잡았는데, 정작 한글
음절의 79%가 빠져 있었죠. 뷁 하나 쓰려면 8바이트 편법을 동원해야 했고, 그마저도 대부분
시스템에서 안 먹혔습니다.
그렇다고 이제 와서 표준을 조합형으로 갈아엎을 순 없었어요. 이미 수많은 문서와 프로그램이 EUC-KR에 맞춰 돌아가고 있었으니까요. 기존 걸 다 깨면서 새 표준으로 이사하는 건 현실적 으로 불가능했습니다. 그럼 방법은 하나예요. 기존 완성형은 손대지 않고, 빈 공간에 나머지 8,822자를 밀어 넣는 것.
이 현실적인 확장을 해낸 주인공이 뜻밖에도 마이크로소프트였습니다.
스토리 — 마이크로소프트가 빈칸을 메우다
마이크로소프트는 Windows에 한글을 넣으면서 통합형 한글 코드(UHC, Unified Hangul Code), 흔히 CP949(코드페이지 949)라 부르는 확장을 만듭니다. 발상은 아주 실용적이었어요.
- 기존 EUC-KR의 2,350자는 있던 자리에 그대로 둔다. (하위 호환)
- EUC-KR이 안 쓰고 비워둔 빈 코드 공간에, 나머지 8,822자를 2바이트로 채워 넣는다.
결과적으로 CP949는 한글 음절 11,172자 전부를 2바이트로 표현하게 됩니다. 7장에서
8바이트 편법으로 겨우 표현하던 뷁이, 이제 어떻게 되는지 바이트로 볼까요.
print("가".encode("euc-kr")) # b'\xb0\xa1' ← 완성형 2,350자
print("가".encode("cp949")) # b'\xb0\xa1' ← 똑같다! EUC-KR을 그대로 품음(하위 호환)
print("뷁".encode("euc-kr")) # b'\xa4\xd4\xa4\xb2\xa4\xce\xa4\xaa' ← 8바이트 편법
print("뷁".encode("cp949")) # b'\x94\xee' ← 2바이트로 깔끔하게 해결!가는 EUC-KR이든 CP949든 똑같은 0xB0 0xA1이에요. 즉 기존 EUC-KR 파일은 CP949로 그대로
열립니다. 아무것도 안 깨져요. 그러면서 뷁 같은 음절까지 2바이트로 담아냈으니, 한국
개발자에겐 사실상 구원이었습니다. CP949는 Windows의 압도적인 점유율을 타고 한국의 사실상
표준이 됐어요. 국가가 정한 순수 표준이 아니라 벤더(MS)의 코드페이지가 표준 자리를
차지한 거죠. (5장에서 Windows가 Latin-1을 확장한 CP1252로 서유럽을 장악한 것과 판박이예요.)
핵심 — 하위 호환의 승리, 그리고 되풀이된 함정
CP949가 잘한 건 분명합니다. 하위 호환을 지키면서 확장했다는 것. 기존 자산을 하나도 안 깨고 빈 곳만 채우는 이 전략은, 오늘날 소프트웨어 버전을 올릴 때도 그대로 통하는 정석이에요. 리누스 토르발스가 리눅스 커널에 못 박은 “유저 공간을 깨지 마라(don’t break userspace)” 와 같은 정신입니다.
하지만 CP949도 6·7장에서 예고된 그 함정을 끝내 피하지 못했습니다.
확장 영역 글자 중 일부는, 2번째 바이트가 ASCII 문자와 겹칩니다.
b = "똠".encode("cp949") # b'\x8cc'
print(b[0], b[1]) # 140 99 ← 두 바이트
print(chr(b[1])) # c ← 두 번째 바이트(0x63)가 하필 ASCII 'c'!똠은 CP949로 0x8C 0x63 두 바이트인데, 두 번째 바이트 0x63이 알파벳 c와 똑같은
값이에요. 무슨 일이 벌어질까요? 문자열을 바이트 단위로 훑으면서 특정 ASCII 문자를
찾는 순진한 코드를 생각해 보세요. 예를 들어 경로에서 /를, 또는 어떤 글자 c를 찾는
코드가, 한글 똠의 두 번째 바이트를 진짜 c로 착각할 수 있습니다. 한글이 든 문자열을
바이트로 자르거나 검색하다가 엉뚱한 곳이 잘리고 깨지는 거예요.
이게 바로 7장 조합형에서 봤고(가의 둘째 바이트가 a), 9장 Shift-JIS에서 더 지독하게
만날 바로 그 병입니다. 선행 바이트는 128 이상이라 구분되지만, 뒤따르는 바이트까지
ASCII를 피하도록 설계하진 못했다. 기존 바이트 배치를 안 건드리려다 보니 어쩔 수
없었어요. 가변 폭 멀티바이트 인코딩이 공통으로 짊어진 숙명입니다.
정리
- 완성형 표준을 못 갈아엎으니, 기존 EUC-KR은 그대로 두고 빈 공간에 나머지 8,822자를 채운 확장이 CP949(=UHC, 통합형 한글 코드, 마이크로소프트)다. 한글 11,172자 전부를 2바이트로.
- 하위 호환의 승리: EUC-KR 파일이 CP949로 그대로 열린다(상위 집합). “기존 걸 깨지 마라”는 실무 확장의 정석.
- 되풀이된 함정: 확장 영역 글자 일부는 2번째 바이트가 ASCII와 겹친다(
똠=0x8C 0x63,0x63=c). 바이트 단위 문자열 처리에서 한글이 깨지는 고전 버그 — 7장 조합형·9장 Shift-JIS와 같은 병. - CP949는 국가 표준이 아니라 벤더(MS)의 코드페이지가 점유율로 표준이 된 사례다.
생각해볼 질문: 하위 호환을 지키려고 2번째 바이트를 ASCII 영역까지 내준 이 맞바꿈 —
만약 당신이 설계자였다면, 호환성을 포기하고 깨끗한 바이트 배치를 택했을까요, 아니면
CP949처럼 기존 자산을 지키는 쪽을 택했을까요? 여기까지가 한국의 사정이었어요. 그런데
이 “2번째 바이트가 ASCII와 겹친다”는 병을, 바다 건너 일본은 훨씬 먼저, 훨씬 지독하게 앓고
있었습니다. 일본의
Shift-JIS에서는 한글도 아닌 역슬래시(\)와 엔화 기호(¥)가 뒤엉키고, 소스 코드가
컴파일되다 깨지는 일까지 벌어져요. 같은 병이 어디까지 갈 수 있는지, 바다 건너로 가봅시다.