5장. 남은 한 비트의 유혹 — 확장 ASCII와 코드페이지

만화로 보는 요약 — 먼저 읽어보세요
확장 ASCII의 각 코드페이지는 같은 8비트 값을 서로 다른 문자로 해석했고, 바이트에 인코딩 정보가 없다는 사실이 국경을 넘는 모지바케를 만들었다.
면접 실전 질문: ① 코드페이지란 무엇인가? ② 같은 0xE9가 다른 글자가 되는 이유는? ③ 바이트만으로 문자를 알 수 없는 이유는?
배경 — 놀고 있는 비트 하나
4장 끝의 그 빈 서랍에서 시작합니다. ASCII는 7비트, 128칸이었죠. 그런데 컴퓨터가 데이터를 다루는 기본 단위는 점점 8비트 = 1바이트로 굳어집니다. 통신이 안정되면서 오류 검사 (parity)용이던 8번째 비트도 슬슬 놀기 시작했고요.
바이트 하나에 8비트. 조합은 256가지. ASCII가 쓰는 건 앞쪽 128가지뿐. 그럼 뒤쪽 128~255번은 공짜로 생긴 빈 방이잖아요? 이 유혹을 참을 수 있는 사람은 없었습니다. 문제는 딱 하나 — 그 방을 뭘로 채울지, 정해주는 사람이 아무도 없었다는 겁니다.
스토리 — 각자 채우기 시작했다
먼저 유럽이 움직입니다. 프랑스엔 é가, 독일엔 ü가, 스페인엔 ñ가 필요했으니까요.
그래서 ISO 8859-1, 흔히 Latin-1이라 부르는 표준이 나옵니다. 128~255번 영역에
서유럽 액센트 글자들을 배정한 약속표예요. 예를 들어 é는 233번(0xE9)입니다.
그런데 러시아는요? 키릴 문자는 Latin-1에 없어요. 그래서 러시아는 KOI8-R 같은 자기
약속표를 만들어 같은 128~255번에 키릴 문자를 넣습니다. 그리스는 그리스 문자를 넣은
ISO 8859-7을 만들었고요. 한편 IBM PC는 그 자리에 선 긋기 문자와 수학 기호를 채운
코드페이지 437을 내장했고, 뒤이어 Windows는 Latin-1을 살짝 고친 CP1252를 썼습니다.
(어디를 고쳤냐면 — Latin-1이 제어용으로 비워둔 128~159번을 Windows가 스마트 따옴표나 €
같은 글자로 채운 거예요. 같은 계열끼리도 이렇게 미묘하게 달랐습니다.)
이런 지역별 단일 바이트 약속표를 통칭 코드페이지(code page) 라고 불러요. 전부 앞쪽 0~127번은 ASCII 그대로 두고, 뒤쪽 128칸만 자기 나라 글자로 갈아 끼운 변종들입니다.
각자 보면 전부 합리적이에요. 자기 나라 안에서는 아무 문제가 없거든요. 문제는 파일이
국경을 넘는 순간 터집니다. 프랑스에서 café라고 저장한 파일의 마지막 바이트는 0xE9
(233번)예요. 이 파일을 러시아 컴퓨터가 KOI8-R로 열면?
data = "café".encode("latin-1") # 프랑스에서 저장: b'caf\xe9'
print(data.decode("latin-1")) # café — 같은 약속표로 읽으면 멀쩡
print(data.decode("koi8-r")) # cafИ — 러시아 약속표로 읽으면?
print(data.decode("cp437")) # cafΘ — IBM PC 약속표로 읽으면?바이트는 한 치도 안 변했습니다. 0xE9 그대로예요. 그런데 읽는 약속표에 따라 é가 И도
됐다가 Θ도 됩니다. 1장의 봉화 — 신호는 멀쩡히 도착했는데 뜻이 뒤집히던 그 사고가,
이제 바이트 단위로, 전 세계에서 매일 벌어지게 된 거예요.
핵심 — 바이트만 보면 아무것도 모른다
코드페이지 시대가 남긴 교훈은 이 책에서 가장 중요한 문장 중 하나입니다.
바이트 자체에는 “무슨 글자인지”가 적혀 있지 않다.
0xE9라는 바이트를 아무리 들여다봐도, 이게 é인지 И인지 Θ인지 알 수 없어요. 답은
바이트 바깥에, “이 파일은 어떤 약속표로 쓰였는가” 라는 정보에 있습니다. 그리고
코드페이지 시대엔 그 정보를 파일 어디에도 적지 않았어요. 그냥 “우리 동네니까 우리
약속표겠지”라고 암묵적으로 가정했을 뿐이죠. 파일이 동네를 벗어나는 순간 그 가정이
깨지고, 글자도 깨집니다.
정리하면 이 시대의 구조적 문제는 세 겹이에요.
- 번호는 같은데 약속표가 다르다. 128~255번이라는 같은 방에 나라마다 다른 글자를 넣었다. 충돌은 설계된 결과다.
- 어떤 약속표인지 기록하지 않았다. 바이트만 오가고, 해석 규칙은 감으로 때려 맞혔다.
- 256칸은 애초에 부족하다. 유럽 글자야 억지로 구겨 넣었다지만 — 한자는 수만 자, 한글 음절도 조합하면 1만 자가 넘어요. 빈 128칸으로는 시작조차 못 하는 문자들이 지구 반대편에서 기다리고 있었습니다.
정리
- 바이트(8비트)가 표준이 되며 ASCII 밖 128~255번이 빈 방으로 생겼고, 나라마다 그 방을 자기 글자로 채운 코드페이지(Latin-1, KOI8-R, ISO 8859-7, CP437, CP1252…)가 난립했다.
- 같은 바이트
0xE9가 약속표에 따라é/И/Θ로 갈린다 — 같은 신호, 다른 약속표의 전 지구화. 파일이 국경을 넘는 순간 글자가 깨진다. - 핵심 교훈: 바이트에는 인코딩 정보가 없다. 어떤 약속표로 읽을지는 바이트 바깥의 정보인데, 이 시대엔 그걸 기록하지 않고 암묵적으로 가정했다.
- 그리고 256칸으로는 한자·한글 같은 대형 문자 집합은 시작조차 불가능하다.
생각해볼 질문: 유럽은 그래도 빈 128칸에 글자를 욱여넣기라도 했어요. 그런데 한자 수만 자, 한글 음절 1만여 자는요? 바이트 하나로 안 되면 — 두 개를 붙이면 됩니다. 바이트 둘이면 65,536칸. 동아시아가 이 아이디어로 자기 글자를 담기 시작하는데, “바이트 하나 = 글자 하나”라는 반세기의 상식이 여기서 처음 무너집니다.