합동회사 코무라소프트
3장

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+007F10xxxxxxx
U+0080〜U+07FF2110xxxxx 10xxxxxx
U+0800〜U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000〜U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

템플릿의 검은 부분(1110 이나 10)은 고정된 「표지」입니다. 선두 바이트의 1 의 개수가 문자의 바이트 수를 알리고, 이어지는 바이트는 반드시 10 으로 시작한다 — 이 규칙만으로, 바이트 열의 어디에서든 문자의 경계를 찾을 수 있습니다.

손 계산 — U+3042 를 3 바이트로 만든다

일본어 문자 「あ」 U+3042 는 U+0800〜U+FFFF 범위이므로 3 바이트 템플릿을 사용합니다. 절차는 2 단계입니다.

Step 1 — 번호를 2 진수로 만들고, 오른쪽부터 6·6 으로 끊는다
0x3042 = 0011 0000 0100 0010. 오른쪽부터 6 비트씩 끊으면 0011 / 000001 / 000010(4 + 6 + 6 비트)
Step 2 — 템플릿의 x 에 앞에서부터 채운다
11100011 10000001 10000010 → 16 진으로 바꾸면 E3 81 82

검산해 봅시다. E3 = 1110 0011 — 선두에 1 이 3 개이므로 「3 바이트 문자의 선두」. 81 = 1000 0001, 82 = 1000 0010 — 둘 다 10 으로 시작하는 「이어지는 바이트」. 표지를 벗겨 내고 나머지를 이으면 0011 000001 000010 = 0x3042 로 돌아옵니다. 가는 길도 오는 길도, 그저 비트의 옮겨 담기입니다.

U+3042 의 16 비트를 4·6·6 으로 끊어, 1110xxxx 10xxxxxx 10xxxxxx 의 템플릿에 흘려 넣어 E3 81 82 를 얻는 그림

코드 포인트의 비트가 템플릿에 흘려 넣어지는 모습. 색이 있는 비트가 「あ」의 번호의 내용이고, 검은 부분은 UTF-8 의 고정된 표지입니다.

소문제 3-1 — 템플릿에 흘려 넣는다

본문의 절차대로, 종이에 2 진수를 쓰면서 확인해 주세요.

Q1. UTF-8 의 바이트 열을 앞에서부터 읽을 때, 그 바이트가 「몇 바이트 문자의 선두인지」는 어떻게 알 수 있습니까.

Q2. U+3042(2 진으로 0011 0000 0100 0010)를 3 바이트의 템플릿 1110xxxx 10xxxxxx 10xxxxxx 에 흘려 넣는 올바른 절차는 어느 것입니까.

Q3. ASCII 문자는 UTF-8 에서 1 바이트, 일본어 한자나 히라가나의 다수는 3 바이트가 됩니다. 문자열 「Hello世界」는 UTF-8 로 몇 바이트입니까.

바이트

왜 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 호환」이 안전하게 성립하는 이유로 올바른 것은 어느 것입니까.

Q5. 일본어 인사말 「こんにちは」(히라가나 5 문자, 모두 3 바이트)는 UTF-8 로 몇 바이트입니까.

바이트

Q6. UTF-8 의 BOM(Byte Order Mark)의 설명으로 올바른 것은 어느 것입니까.

이 장에서 가져갈 것

  • 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 의 안심이 얼마나 귀중했는지 알게 됩니다.