9장. Shift-JIS와 바다 건너의 혼란

만화로 보는 요약 — 먼저 읽어보세요
Shift-JIS의 문자 두 번째 바이트는 역슬래시 같은 구문 문자와 겹칠 수 있어, 글자 하나가 경로 구분자·이스케이프·엔화 기호로 오해되며 시스템을 깨뜨렸다.
면접 실전 질문: ① Shift-JIS의 두 번째 바이트가 위험한 이유는? ② 0x5C는 어떤 두 얼굴을 갖는가? ③ 다메모지는 왜 빌드와 경로를 깨뜨리는가?
배경 — 같은 병, 더 나쁜 자리
8장에서 CP949의 지병을 봤죠. 확장 한글의 2번째 바이트가 ASCII와 겹친다는 것. 그런데
똠의 둘째 바이트가 c였던 건, 사실 운이 좋은 편이었어요. c는 그냥 평범한 글자라,
문자열을 검색하거나 자를 때나 문제가 됐으니까요.
바다 건너 일본은 훨씬 고약한 자리에서 같은 병을 앓았습니다. 하필 겹친 바이트가 0x5C,
바로 역슬래시(\) 였거든요. 역슬래시가 왜 문제냐고요? 이건 그냥 글자가 아니에요.
Windows 경로 구분자(C:\Users), 문자열 이스케이프(\n, \t), 정규식 메타문자 —
시스템이 특별하게 취급하는, 구문(syntax)에 관여하는 바이트입니다. 이게 한자 안에
숨어 있으면 어떻게 될까요?
스토리 — 「表」와 「ソ」의 저주
일본의 Shift-JIS는 이름부터 사연이 있어요. 일본은 이미 1바이트 코드(JIS X 0201)로 반각 가타카나를 쓰고 있었는데, 여기에 한자(JIS X 0208)를 끼워 넣어야 했습니다. 그래서 기존 1바이트 글자들이 차지한 자리를 피해, 남는 틈으로 한자 코드를 밀어 넣었어요(shift). 그래서 이름이 Shift-JIS입니다.
문제는 이 “남는 틈”을 찾다 보니, 2바이트 한자의 두 번째 바이트가 0x40~0x7E 범위까지
내려왔다는 거예요. 이 범위엔 알파벳, 숫자, 그리고 하필 역슬래시(0x5C) 가 들어
있습니다. 실제로 찍어볼까요.
b = "表".encode("shift_jis") # 일본어 '표(表)'
print(b) # b'\x95\\' ← 두 번째 바이트가 0x5C
print(b[1] == ord("\\")) # True ← 그 바이트는 역슬래시와 똑같다!表(겉 표)는 Shift-JIS로 0x95 0x5C인데, 두 번째 바이트 0x5C가 역슬래시와 완전히
같은 값이에요. 表만이 아닙니다. 十, 予, ソ, 構, 能… 두 번째 바이트가 0x5C인
글자가 수두룩했어요. 일본 개발자들은 이런 글자를 「ダメ文字(다메모지)」, 우리말로 “문제아
글자”라고 불렀습니다. 특히 가타카나 「ソ」(소) 는 이 문제의 상징이 됐죠.
핵심 — 바이트가 글자이자 구문일 때
왜 이게 CP949보다 지독했을까요? 역슬래시가 구문에 관여하는 바이트이기 때문입니다.
가장 유명한 사고는 소스 코드였어요. C나 자바로 문자열을 짤 때 리터럴에 한자를 적으면,
컴파일러가 한자의 둘째 바이트 0x5C를 이스케이프 시작으로 착각합니다. 바이트로
직접 보면 왜 터지는지 눈에 보여요.
print("十".encode("shift_jis")) # b'\x8f\\' ← 十 = 0x8F 0x5C (파이썬은 0x5C를
# 역슬래시로 알아 \\로 표시 — 이게 이 장의 전부!)
src = '"十"' # 소스에 문자열 "十" 을 그대로 적으면
print(src.encode("shift_jis")) # b'"\x8f\\"' ← 0x5C 뒤의 " 가 \" 로 붙어버린다!보이시나요? 十의 둘째 바이트 0x5C 바로 뒤에 닫는 따옴표 "가 오니까, 컴파일러 눈엔
\"(이스케이프된 따옴표)로 보여요. 문자열이 안 닫힙니다. 한자 한 글자 때문에 빌드가 깨지는 황당한 일이 실제로 벌어진 거예요. (6장에서 “그 사고는 9장에서”라며 미뤄뒀던 게
바로 이겁니다.)
파일 경로도 마찬가지였어요. 表.txt 같은 이름이 바이트로는 ...\x5C...이니, 경로를
바이트로 훑는 코드가 0x5C를 디렉터리 구분자로 오해해서 엉뚱한 곳을 가리켰죠.
여기에 저주가 한 겹 더 있었습니다. 일본에선 0x5C라는 바이트 자체가 역슬래시가 아니라
엔화 기호(¥) 로 표시됐거든요. 초기 일본어 코드(JIS X 0201)가 ASCII의 역슬래시 자리
(0x5C)를 엔화로 바꿔 놨기 때문이에요. 그래서 같은 0x5C 하나가 어떤 화면에선 \,
어떤 화면에선 ¥ 로 보였습니다. 일본 Windows에서 경로가 C:¥Users처럼 보이던 게
이것 때문이에요. 바이트 하나에 얼굴이 둘, 5장에서 본 “같은 번호 다른 글자”의 가장
악명 높은 사례죠.
정리하면 Shift-JIS의 교훈은 이겁니다. 바이트가 데이터이자 구문일 때, 그 자리를 어설프게
침범한 인코딩은 검색·자르기를 넘어 시스템 전체를 오작동시킨다. CP949의 c는 불편이었지만,
Shift-JIS의 \는 컴파일러와 파일 시스템을 무너뜨리는 무기였어요.
정리
- 일본 Shift-JIS는 기존 1바이트 코드(JIS X 0201)의 틈에 한자를 밀어 넣은(shift) 가변 폭
인코딩. 그 결과 한자의 2번째 바이트가
0x40~0x7E까지 내려와 ASCII와 겹쳤다. - 하필 겹친 자리에 역슬래시(
0x5C) 가 있었다(表=0x95 0x5C,十·予·ソ·構…). 역슬래시는 경로 구분자·이스케이프·정규식 메타문자라, 한자 하나가 소스 컴파일과 파일 경로를 깨뜨렸다(「ダメ文字」). - 이중의 저주:
0x5C는 일본에서 역슬래시이자 엔화(¥) 로도 표시됐다(JIS X 0201이 역슬래시 자리를 엔화로 대체). 바이트 하나에 얼굴이 둘. - 교훈: 바이트가 데이터이자 구문일 때, 그 자리를 어설프게 침범한 인코딩은 검색·자르기를
넘어 시스템 전체를 오작동시킨다. CP949의
c보다 Shift-JIS의\가 훨씬 파괴적이었다.
생각해볼 질문: 자, 1장부터 여기까지 오면서 우리는 온갖 깨짐을 봤어요. 봉화의 오해,
코드페이지의 é→И, 한글 8바이트 편법, 그리고 방금 表 → 빌드 붕괴까지. 그런데 이
모든 사고에는 딱 하나의 공통 원인이 있습니다. 그게 뭘까요? 이제 깨진 글자 ???,
모지바케의 정체를 정면으로 해부할 시간입니다. 그리고 그 해부가 끝나면, 마침내 이
모든 혼란을 끝낼 유니코드가 등장해요.