UTF-8 을 손으로 조립한다
코드 포인트의 비트를 1〜4 바이트의 템플릿에 흘려 넣는다 — UTF-8 의 변환을 U+3042 → E3 81 82 로 손 계산하고, ASCII 호환의 이유와 BOM 까지 확인한다.
「왜 3 바이트인가」를, 계산으로 답할 수 있게 된다
「UTF-8 이면 일본어는 3 바이트」 — 어디선가 들어 본 적은 있어도, 왜 3 인지, 그 3 바이트의 내용이 무엇인지 설명할 수 있을까요. 이 장의 목표는 1 가지, U+3042 「あ」가 E3 81 82 가 되기까지를 자신의 손으로 도출하는 것입니다. 한 번 도출하면, UTF-8 은 암기물이 아니라 「계산할 수 있는 것」으로 바뀝니다.
UTF-8 은 가변 길이 인코딩입니다. 코드 포인트의 크기에 따라, 1 문자를 1〜4 바이트로 나타냅니다.
| 코드 포인트의 범위 | 바이트 수 | 템플릿(x 에 번호의 비트를 흘려 넣는다) |
|---|---|---|
| U+0000〜U+007F | 1 | 0xxxxxxx |
| U+0080〜U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800〜U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000〜U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
템플릿의 검은 부분(1110 이나 10)은 고정된 「표지」입니다. 선두 바이트의 1 의 개수가 문자의 바이트 수를 알리고, 이어지는 바이트는 반드시 10 으로 시작한다 — 이 규칙만으로, 바이트 열의 어디에서든 문자의 경계를 찾을 수 있습니다.
손 계산 — U+3042 를 3 바이트로 만든다
일본어 문자 「あ」 U+3042 는 U+0800〜U+FFFF 범위이므로 3 바이트 템플릿을 사용합니다. 절차는 2 단계입니다.
0011 0000 0100 0010. 오른쪽부터 6 비트씩 끊으면 0011 / 000001 / 000010(4 + 6 + 6 비트)11100011 10000001 10000010 → 16 진으로 바꾸면 E3 81 82검산해 봅시다. E3 = 1110 0011 — 선두에 1 이 3 개이므로 「3 바이트 문자의 선두」. 81 = 1000 0001, 82 = 1000 0010 — 둘 다 10 으로 시작하는 「이어지는 바이트」. 표지를 벗겨 내고 나머지를 이으면 0011 000001 000010 = 0x3042 로 돌아옵니다. 가는 길도 오는 길도, 그저 비트의 옮겨 담기입니다.
코드 포인트의 비트가 템플릿에 흘려 넣어지는 모습. 색이 있는 비트가 「あ」의 번호의 내용이고, 검은 부분은 UTF-8 의 고정된 표지입니다.
소문제 3-1 — 템플릿에 흘려 넣는다
본문의 절차대로, 종이에 2 진수를 쓰면서 확인해 주세요.
Q1. UTF-8 의 바이트 열을 앞에서부터 읽을 때, 그 바이트가 「몇 바이트 문자의 선두인지」는 어떻게 알 수 있습니까.
UTF-8 은 선두 바이트 자신이 길이를 알리는 설계입니다. 선두의 1 의 개수가 그대로 문자의 바이트 수(110→2, 1110→3, 11110→4)이며, 이어지는 바이트는 모두 10xxxxxx. 이 규칙 덕분에 중간 바이트부터 읽기 시작해도 문자의 경계를 곧바로 찾을 수 있습니다. 고정 길이가 아니라 1〜4 바이트의 가변 길이입니다.
Q2. U+3042(2 진으로 0011 0000 0100 0010)를 3 바이트의 템플릿 1110xxxx 10xxxxxx 10xxxxxx 에 흘려 넣는 올바른 절차는 어느 것입니까.
템플릿의 x 는 앞에서부터 4 개·6 개·6 개입니다. 코드 포인트의 비트 열을 오른쪽부터 6·6 으로 끊으면 0011 / 000001 / 000010 이 되고, 채우면 11100011 10000001 10000010 = E3 81 82. 16 진의 30 42 를 「그대로 놓는」 방식은 UTF-16 의 발상이며, UTF-8 은 비트의 재배치를 한다는 점이 다릅니다.
Q3. ASCII 문자는 UTF-8 에서 1 바이트, 일본어 한자나 히라가나의 다수는 3 바이트가 됩니다. 문자열 「Hello世界」는 UTF-8 로 몇 바이트입니까.
Hello 는 ASCII 5 문자 × 1 바이트 = 5 바이트, 「世」(U+4E16)와 「界」(U+754C)는 모두 U+0800〜U+FFFF 범위이므로 3 바이트씩 6 바이트. 합계 11 바이트입니다. 「문자 수 = 바이트 수」가 성립하지 않음을, 구체적인 수로 몸에 익혀 둡시다. 데이터베이스의 열 폭 설계 등에서 반드시 효력을 발휘합니다.
왜 ASCII 호환이 「통하는가」
1 바이트 템플릿 0xxxxxxx 를 잘 보세요. U+0000〜U+007F, 즉 ASCII 의 128 문자는, ASCII 시대와 완전히 같은 1 바이트가 됩니다. 영숫자만의 파일은 ASCII 로 읽어도 UTF-8 로 읽어도 동일 — 기존의 프로그램이나 프로토콜을 거의 바꾸지 않고 이행할 수 있었던 것은, 이 설계 덕분입니다.
또 1 가지, 놓치기 쉬운 급소가 있습니다. 2 바이트 이상의 문자를 구성하는 바이트는, 선두(11 로 시작)도 이어짐(10 으로 시작)도 반드시 0x80 이상입니다. 즉, 일본어 문자의 바이트 열 중간에 /(0x2F)나 "(0x22) 같은 ASCII 기호와 같은 값이 절대로 나타나지 않습니다. 구분 문자를 바이트 단위로 찾는 프로그램이, 문자의 중간을 잘못 구분하는 사고가 구조적으로 일어나지 않는 것입니다. 이 안심이 당연한 것이 아니었음을, 다음 장의 Shift_JIS 에서 뼈저리게 알게 됩니다.
시뮬레이터로 확인한다
아래 시뮬레이터에 원하는 문자열을 넣으면, 이 장에서 손 계산한 변환을 한꺼번에 확인할 수 있습니다. 우선 초기값인 일본어 인사말 「こんにちは」로, 5 문자가 15 바이트가 되는 것, 어느 문자나 E3 로 시작하는 것을 봐 주세요. 자신의 이름, 이모지, 영어·한국어가 섞인 문자열도 추천합니다. 입력은 브라우저 안에서만 처리되며, 서버에는 전송되지 않습니다.
시뮬레이터의 동작 개요(텍스트판): 텍스트 입력란에 문자열을 넣으면, 문자마다 코드 포인트(U+XXXX)·UTF-8 의 바이트 열(16 진)·UTF-16 의 코드 단위가 표로 갱신됩니다. 아울러 UTF-8 의 총 바이트 수·UTF-16 의 코드 단위 수·코드 포인트 수·눈에 보이는 문자 수(그래핌)의 4 가지 수치가 표시되고, 마지막에 「이 UTF-8 바이트 열을 Shift_JIS 로 오독하면 어떻게 보이는가」가 표시됩니다(사용 중인 브라우저가 Shift_JIS 디코딩을 지원하지 않는 경우, 그 란에는 「사용 중인 브라우저에서는 표시할 수 없습니다」라는 취지가 표시됩니다).
BOM — 파일 선두의 작은 「표시」
시뮬레이터의 마지막 란에서, UTF-8 의 바이트 열을 Shift_JIS 로 오독한 모습을 보았습니다. 「읽는 쪽이 인코딩을 알아맞힐 수 있도록, 파일 선두에 표시를 둘 수 없을까」 — 그 답의 1 가지가 BOM(Byte Order Mark)입니다. 코드 포인트 U+FEFF 를 UTF-8 로 인코딩한 EF BB BF 의 3 바이트를 파일 선두에 둡니다.
이름 그대로, 본래는 UTF-16 에서 바이트의 나열 순서(상위가 먼저인가 하위가 먼저인가)를 나타내기 위한 구조입니다. UTF-8 에는 바이트 순서 문제가 없으므로, BOM 은 순수하게 「이것은 UTF-8 입니다」라는 임의의 표시로 사용됩니다. 붙어 있으면 단서가 되는 한편, 선두 3 바이트를 상정하지 않은 프로그램에는 쓸데없는 데이터로 오동작의 씨앗이 되기도 합니다. 「BOM 은 필수가 아니다. 다만 Excel 등, BOM 에 기대어 인코딩을 판정하는 소프트웨어가 있다」 — 이 줄다리기의 실무적 귀결은 제 6 장에서 다룹니다.
소문제 3-2 — ASCII 호환과 BOM
UTF-8 이 세계 표준이 될 수 있었던 이유와, 파일 선두의 「표시」를 확인합니다.
Q4. UTF-8 의 「ASCII 호환」이 안전하게 성립하는 이유로 올바른 것은 어느 것입니까.
0x00〜0x7F 는 ASCII 와 비트 단위로 동일하고, 2 바이트 이상의 문자를 구성하는 바이트(선두도 이어짐도)는 반드시 최상위 비트가 1, 즉 0x80 이상입니다. 그래서 영숫자만의 파일은 그대로 유효한 UTF-8 이며, 반대로 일본어의 중간에 0x2F(/) 같은 ASCII 값이 나타나는 일도 없습니다. 이 설계가, 기존의 ASCII 전제 프로그램과 공존할 수 있었던 최대의 이유입니다. 제 4 장에서 보는 Shift_JIS 는 바로 여기서 문제를 안게 됩니다.
Q5. 일본어 인사말 「こんにちは」(히라가나 5 문자, 모두 3 바이트)는 UTF-8 로 몇 바이트입니까.
3 바이트 × 5 문자 = 15 바이트입니다. E3 81 93(こ) E3 82 93(ん) E3 81 AB(に) E3 81 A1(ち) E3 81 AF(は)로, 어느 문자나 E3 로 시작한다는 점에 주목해 주세요 — 히라가나의 코드 포인트(U+3040 대)가 가깝기 때문에 선두 바이트가 갖추어지는 것입니다. 이 「E3 투성이」가, 제 7 장에서 문자 깨짐의 지문으로 효력을 발휘합니다.
Q6. UTF-8 의 BOM(Byte Order Mark)의 설명으로 올바른 것은 어느 것입니까.
BOM 의 실체는 U+FEFF 를 인코딩한 EF BB BF 의 3 바이트입니다. UTF-16 에서는 바이트 순서(어느 바이트가 먼저인가)를 나타내는 실용적인 역할이 있지만, UTF-8 에 바이트 순서 문제는 없으므로 순수하게 「UTF-8 입니다」라는 표시로만 기능합니다. 필수는 아니며, 붙이면 선두 3 바이트를 상정하지 않는 프로그램이 오동작하는 일도 있습니다. 한편 Excel 의 CSV 인식에는 BOM 이 효력을 발휘한다 — 이 실무의 줄다리기는 제 6 장에서 다룹니다.
이 장에서 가져갈 것
- UTF-8 은 1〜4 바이트의 가변 길이. 선두 바이트의 1 의 개수가 바이트 수를 알리고, 이어짐은 반드시 10 으로 시작한다
- U+3042 → 0011 / 000001 / 000010 → E3 81 82. 변환은 비트의 옮겨 담기에 지나지 않는다
- ASCII 는 UTF-8 에서도 같은 1 바이트, 다바이트 문자의 구성 바이트는 모두 0x80 이상 — 이 2 가지가 ASCII 호환의 핵심
- BOM 은 U+FEFF 의 인코딩 EF BB BF. UTF-8 에서는 필수가 아닌 「표시」
다음 장은, UTF-8 보다 전부터 일본어를 지탱해 온 Shift_JIS 로. 「다바이트 문자의 중간에 ASCII 의 값이 나타나지 않는다」는 UTF-8 의 안심이 얼마나 귀중했는지 알게 됩니다.