noocyte のプログラミング研究室 〜プログラムは楽しげに走らねばならない♪〜

文字コードに関する覚え書きと実験

公開:2007/05/06(日)
最終更新:2016/04/09(土)

文字コードについて調べたことや実験したこと, テストプログラム,データファイルなどを随時掲載する予定です. ただし筆者の理解不足や誤解により誤りがあるかもしれませんので, ご利用は自己責任で.

このページの主な更新は Blog でお知らせします.

表示確認ブラウザ:FireFox 22.0,IE8.


0.目次

  1. シフトJIS
    1. Shift_JIS と Windows-31J (CP932) の違い
    2. シフトJIS 2バイト文字の判定
    3. 謎の検索ワード集 (シフトJIS編)
      • 「Shift_JIS(SJIS,Windows-31J,CP932) 3バイト文字」
      • 「Shift_JIS(SJIS,Windows-31J,CP932) サロゲート(ペア)」
      • 「UTF-8 4バイト文字 Shift_JIS(SJIS,Windows-31J,CP932) 変換」
      • 「Unicode(UTF-8,UTF-16) から Shift_JIS(SJIS,Windows-31J,CP932) へ変換できない文字(一覧)」
  2. Win32 API の MultiByteToWideChar() は CP932 (Microsoft 版シフトJIS) をどのように変換するか? (書きかけ)
  3. 日本語文字コードの自動判別 (実験準備中)
  4. UTF-16 形式 (UTF 16-bit)
    1. サロゲート
  5. UTF-8 形式 (UTF 8-bit)
    1. 現行の UTF-8 (RFC3629)
    2. 旧 UTF-8 (RFC2279(廃))
  6. Unicode 関数・マクロ集 (C言語)
    1. UTF-16 符号単位がサロゲートか否かを判定する.
    2. サロゲート・ペア ⇔ Unicode スカラ値変換
    3. UTF-16 文字列関数
    4. UTF-8/CESU-8 文字列関数・マクロ
  7. 文字コード雑記
    1. 謎の検索ワード集 (文字コード一般)
    2. ASCII の NUL と DEL の本来の意味
  8. 文字コード関連データファイル
  9. サイト内関連ページ
  10. 外部へのリンク
  11. 参考図書
  12. 更新履歴

1.シフトJIS

1.1 Shift_JIS と Windows-31J (CP932) の違い

標準的なシフトJIS (以下 Shift_JIS) が扱う2バイト文字は,JIS X 0208 の 1〜94 区であるが,ベンダ独自拡張版のシフトJIS (Windows-31J:Microsoft のコードページ 932 (以下 CP932) など) では,JIS X 0208 には存在しない 95〜120 区も扱う.

シフトJISにおいて,2バイト文字の第1バイト (LeadByte), 第2バイト (TrailByte),区番号,点番号の範囲は次のとおり.


IANA登録名
(別名)
Shift_JIS
(MS_Kanji,csShiftJIS)
Windows-31J
(csWindows31J)
別名 (非IANA) SJIS(*1) CP932
位置づけ 標準版 ベンダ独自拡張版
収録文字数 7,070文字 7,517文字 (重複を除く)
2バイト文字 文字集合 JIS X 0208 (6,879文字) JIS X 0208 (6,879文字)
ベンダ拡張文字 (447文字)
(ユーザ定義外字領域)
(合計7,326文字 (重複を除く))
区番号 1〜94 1〜120
点番号 1〜94
第1バイト
(LeadByte)
0x81〜0x9F
0xE0〜0xEF
0x81〜0x9F
0xE0〜0xFC
第2バイト
(TrailByte)
0x40〜0x7E
0x80〜0xFC
1バイト文字 制御文字
(JIS X 0211 C0集合)
(34文字)
0x00〜0x1F,0x20,0x7F
(ASCII 制御文字と同じ.0x20 (空白) は制御
文字とも,印字しない図形文字ともみなされる.)
図形文字
(JIS X 0201)
(157文字)
ラテン文字用図形文字 (ASCII とほぼ互換):0x21〜0x7E
片仮名用図形文字 (いわゆる半角カナ):0xA1〜0xDF
予約 0x80,0xA0,0xFD〜0xFF(*2)
未定義 0xF0〜0xFC -

TrailByte が取りうる値は 188 (=94×2) 通りで, これは点番号の範囲の丁度2倍になるように設計されたものである. つまり一つの LeadByte の値に対して,2区分の符号位置範囲が対応する. したがって LeadByte の取りうる値の範囲は,区番号の範囲の半分 (Shift_JIS では 94/2=47 通り,Windows-31J では 120/2=60 通り) である.

シフトJISの2バイト文字と区点番号は次のようにして対応付けられる.

  1. 両者の符号位置をそれぞれ小さい順に並べる.
  2. 同じ順位にある符号位置同士を対応させる.

なお JIS X 0208 の文字については,区番号および点番号それぞれに 0x20 を加算すると JIS コード,0xA0 を加算すると EUC-JP になる.

参考:シフトJIS / JIS X 0208 文字コード表 (現在の表示環境におけるシフトJISの2バイト文字集合を表示)



(*1) 現実には,“SJIS”や“シフトJIS”などは元の文字集合を区別せず (あるいは区別を理解せず) に標準版もベンダ拡張版もごっちゃにして使われていることが多い.
(*2) 0xFD〜0xFF については,数年前に古川享氏 (元・日本マイクロソフト株式会社社長・会長) のブログの「シフトJISの産まれた歴史的背景」 という記事で次のような内容が書かれていたと思うが, そのブログは現在削除されているので確認できない.

将来の CP/M のバージョンアップで,これらが制御文字として使用されることになりそうだったので予約としたが, 結局 CP/M はそうしなかった.(大意・うろ覚え)


シフトJISの2バイト文字と区点番号の対応表
[画像:シフトJISの2バイト文字と区点番号の対応表]

1.2 シフトJIS 2バイト文字の判定

/*─────────────────────────────────────
入力  :byte:0x00〜0xFF.
戻り値:byte がシフトJIS 2バイト文字の第1バイトのときそのときに限り真.
2007/10/28(日) Shift_JIS (1〜94区) 限定版を追加.
2008/06/24(火) 改定
2010/03/18(木) 高速版の不等号の右辺の意味がわかりやすいように定数の表記変更.
─────────────────────────────────────*/
// 定義どおりの素直な判定方法.
// (わかりやすいが,範囲の判定方法に無駄があるのであまり賢くない.)
// Shift_JIS (1〜94区) 限定版
#define IsSjisLeadByte(byte) \
  (ValueInRangeInclusive(0x81U, (unsigned)(byte), 0x9FU) || \
   ValueInRangeInclusive(0xE0U, (unsigned)(byte), 0xEFU))
// ベンダ拡張版 (1〜120区) 用
#define IsSjisLeadByteX(byte) \
  (ValueInRangeInclusive(0x81U, (unsigned)(byte), 0x9FU) || \
   ValueInRangeInclusive(0xE0U, (unsigned)(byte), 0xFCU))

// ┌ここで見つけた巧妙な判定方法.
// ↓(上の方法に比べ,条件分岐が2〜4回から1回に減るので少し高速化できそう.)
// 初級C言語Q&A(15)【シフトJISの1バイト目の判定】
// Shift_JIS (1〜94区) 限定版
#define IsSjisLeadByte(byte) \
  ((((unsigned)(byte) ^ 0x20U) - 0xA1U) < (unsigned)(94/2))
// ベンダ拡張版 (1〜120区) 用
#define IsSjisLeadByteX(byte) \
  ((((unsigned)(byte) ^ 0x20U) - 0xA1U) < (unsigned)(120/2))

// 2010/03/05(金) 追記
//   この方法は LeadByte を0から始まる連続領域に移動 (「逆」シフト) させるので,
//   SJIS ⇒ 区点/JIS/EUC 変換に応用すれば判定と変換 (の一部) をまとめて行える.

/*─────────────────────────────────────
入力  :byte:0x00〜0xFF.
戻り値:byte がシフトJIS 2バイト文字の第2バイトのときそのときに限り真.
2008/06/24(火) 改定
2010/03/03(水) 条件分岐を1回削減したバージョンを追加.
2010/03/18(木) 高速版の不等号の右辺の意味がわかりやすいように定数の表記変更.
─────────────────────────────────────*/
// 定義どおりの (以下同文)
#define IsSjisTrailByte(byte) \
  (ValueInRangeInclusive(0x40U, (unsigned)(byte), 0xFCU) && ((unsigned)(byte) != 0x7FU))

// 上の巧妙な方法に倣って,条件分岐を1回削減したバージョン.
#define IsSjisTrailByte(byte) \
  ((((unsigned)(byte) - 0x40U) < (unsigned)(94 * 2 + 1)) && \
   ((unsigned)(byte) != 0x7FU))

1.3 謎の検索ワード集 (シフトJIS編)

「Shift_JIS(SJIS,Windows-31J,CP932)  3バイト文字」

1.1 の表に書いてあるとおり, シフトJISには3バイト以上の文字なんか一つもありません!

UTF-8EUC-JIS-2004 の3バイト文字とごっちゃになってる?

それともどこかでガセネタ掴まされた?
(この検索ワードは某大手N社とH社 (たぶん子会社を含む) からのアクセスが多い気がする. もしかして,これら2社のグループで誰かが嘘を教えているのか?)

「Shift_JIS(SJIS,Windows-31J,CP932)  サロゲート(ペア)」

「サロゲート (ペア)」は Unicode (正確には UTF-16) の概念であってシフトJISには関係ありません!
… というツッコミは置いといて …

まあ,Unicode の補助文字 (supplementary character:U+010000 〜 U+10FFFF,UTF-16 においてサロゲート・ペアで表される,俗に言うサロゲート(ペア)文字 (誤称)) をシフトJISに変換したいということなんだろうけど, そんな文字は Shift_JIS (標準的なシフトJIS) や Windows-31J (Windows 版シフトJIS,別名 CP932) には一文字も含まれていない. したがってこの意味でもシフトJISと「サロゲート」は関係ないし,もちろん変換もできない.


2015/07/31(金) 追記・改訂

ただし「シフトJIS」の文字の中でも次のものは Unicode の補助文字に対応する.

  • Shift_JIS-2004
    Shift_JIS-2004 の2バイト文字は JIS X 0213 を符号化したものであり,(Wikipedia によると) そのうちの303文字が Unicode の補助文字 (CJK統合漢字 拡張B領域) に含まれている.
    (でも Shift_JIS-2004 って使われているの? 聞いたことない.ついでに言うと,Windows-31J や MacJapanese と互換性がない.)
  • MacJapanese
    MacJpanese のことはよく知らないけど,Wikipedia の記事をざっと読むと,「0. 」(ゼロ+ピリオド) の1文字だけが Unicode の補助文字 U+1F100 (DIGIT ZERO FULL STOP) に対応しているようだ.
    (参考:MacJapaneseからUnicodeへの変換に必要な私用領域の6文字)
  • 携帯電話の絵文字 (Unicode 6.0)
    Unicode 6.0 (2010) で携帯電話の絵文字が採用された.

2013/04/17(水) 追記

以前からこれらの検索ワードが多いので, 「なんでこんなにシフトJISとサロゲートが変換できると思い込んでいる (らしい) 人が多いんだろう?」とずっと不思議に思っていた. ところが今日,「機種依存文字 環境依存文字 サロゲート …」で検索してきた人と,「髙(はしごだか) サロゲート」で検索して来た人がいた.

そうか,謎はすべて解けた!
Unicode のサロゲートはシフトJISの機種依存文字 (Windows-31J の独自拡張部分) に対応するものだとこっぴどく勘違いしているのか!

しかしここで新たな謎が….なんでそういう誤解をしたんだろう?
誤解の発生過程を憶測してみる.

  1. 「シフトJISの機種依存文字は,他機種では文字化けしたり表示されなかったりして厄介である.」
  2. 「Unicode にはサロゲートとかいうよくわからないものがあって,厄介なものらしい.」
  3. 「そうか,サロゲートって機種依存文字のことかー!」(← 違います!)
「UTF-8 4バイト文字 Shift_JIS(SJIS,Windows-31J,CP932) 変換」

UTF-8 の4バイト文字は補助文字 (俗称・誤称:サロゲート文字) だってことは理解してますか?
上に書いたとおりShift_JIS (標準的なシフト JIS) や Windows-31J/CP932 (Windows 版シフト JIS) には Unicode の補助文字に対応する文字は一つもないから変換できないと何度言えば(ry

2015/07/31(金) 追記

携帯電話の絵文字や Shift_JIS-2004 の一部の漢字は Unicode の補助文字に対応する.

「Unicode(UTF-8,UTF-16) から Shift_JIS(SJIS,Windows-31J,CP932) へ変換できない文字(一覧)」

Unicode 約11万文字の中で Shift_JIS に変換できるのはわずか 6〜7% に過ぎませんよ.
全部知りたいんですか?

  • Shift_JIS の収録文字数:7,070文字 (JIS X 0201:191文字,JIS X 0208:6,879文字)
  • Windows-31J (CP932) の収録文字数:7,517文字 (重複を除く)
  • Unicode 7.0 の収録文字数:113,021文字
  • Unicode から Shift_JIS に変換できない文字:113,021 − 7,070 = 105,951文字
  • Unicode から Windows-31J に変換できない文字:113,021 − 7,517 = 105,504文字
  • Shift_JIS/Windows-31J から Unicode に変換できない文字:なし

変換できない文字の一覧を検索して来る人も多いけど,そんなもの (ほとんど絶対) 誰も作っていないと思う.
(変換できる文字より変換できない文字の方がはるかに多いので, そんな表を作るのはアホらしい.)


2.Win32 API の MultiByteToWideChar() は CP932
  (Microsoft 版シフトJIS) をどのように変換するか?

書きかけです.

テスト方法

準備中

1バイト文字の変換

dwFlags=MB_ERR_INALID_CHARS の場合

dwFlags=0 の場合

2バイト文字の変換

全般的なまとめについては準備中. 個別の2バイト文字の変換結果については, 「Windows-31J (CP932) 文字コード表」 (Windows-31J のすべての2バイト文字を MultiByteToWideChar() で変換したファイル (UTF-8/16BE)) を参照.

関連リンク


3.日本語文字コードの自動判別

実験準備中.

対象文字コード:

とりあえず関連リンク.


4.UTF-16 形式 (UTF 16-bit, RFC2781,2000年2月)

Unicode 1文字を16ビットで表す. ただし第1〜16面 (U+010000 〜 U+10FFFF) の文字にはサロゲート・ペアを使用する.

4.1 サロゲート

サロゲート・ペアは本来 UTF-16 のものであり,それ以外では (UTF-16 をベースにした) UTF-7,そして残念ながら混乱の元となる CESU-8 でのみ用いられる (はず).

サロゲート・ペア内での UTF-16 符号単位の順序は (上位,下位) の順.


2013/08/10(土)
サロゲート(ペア)を壊滅的に理解できていない (どころか独創的に誤解している) 人があまりにも多いようなので追記.
(Unicode 以前からシフト JIS や EUC-* などのマルチバイト文字列を (ライブラリに頼らず自力で) 処理してきたプログラマにとっては,サロゲートも本質的に同じなので朝飯前のはず. (符号単位が1バイトから2バイトに変わっただけ.))


Unicode スカラ値 (実質21bit) UTF-16 符号単位
BMP文字
(0面:U+0000 〜 U+FFFF)
┏━━━┯━━━━━━━━━━┓
┃0 0000xxxx xxxx  xxxx xxxx┃… スカラ値
┗━━━┷━━━━━━━━━━┛
├- 面 -┼--- 区 --┼--- 点 --┤
  (5bit)    (8bit)     (8bit)

┏━━━━━━━━━━┓
┃xxxx xxxx xxxx xxxx ┃
┗━━━━━━━━━━┛

補助文字
(1面〜16面:
U+10000 〜 U+10FFFF)
   w wwww xxxx xxyy yyyy yyyy … スカラ値
-) 0 0001 0000 0000 0000 0000 … 補助面の最初の
───────────────   文字のスカラ値
   0 zzzz xxxx xxyy yyyy yyyy … 補助面内での連番
                                 (実質20bit)
(面番号:wwwww=00001〜10000)
   サロゲートペア
┏━━━━━━━━━━┓
┃1101 10zz zzxx xxxx ┃… 上位サロゲート符号単位
┗━━━━━━━━━━┛   (U+D800 〜 U+DBFF)
┏━━━━━━━━━━┓
┃1101 11yy yyyy yyyy ┃… 下位サロゲート符号単位
┗━━━━━━━━━━┛   (U+DC00 〜 U+DFFF)


5.UTF-8 形式 (UTF 8-bit)

5.1 現行の UTF-8 (RFC3629,2003年11月)


Unicode スカラ値
(実質21bit)
UTF-8 符号単位
第1バイト 第2バイト 第3バイト 第4バイト
UTF8-1
0面0区0〜127点 (ASCII)
U+0000 〜 U+007F
(0000 0000 0xxx xxxx)
    面       区        点
├ 5bit ┼- 8bit -┼- 8bit -┤
┏━━━┯━━━━┯━━━━┓
┃ 00000  00000000  0xxxxxxx┃
┗━━━┷━━━━┷━━━━┛
                    ├-7bit-┤
                       ASCII
0xxx xxxx
(0x00〜0x7F)
(ASCII)
- - -
UTF8-2
0面0区128点〜7区
U+0080 〜 U+07FF
(0000 0xxx xxyy yyyy)
┏━━━━━━━━━━━━┓
┃00000 00000 xxxxx yyyyyy┃
┗━━━━━━━━━━━━┛
│   10bit  │ 5bit| 6bit │
110x xxxx
(0xC2〜0xDF)
10yy yyyy
(0x80〜0xBF)
- -
UTF8-3
0面8〜255区
U+0800 〜 U+D7FF
U+E000 〜 U+FFFF
(xxxx yyyy yyzz zzzz)

Surrogate 領域
(U+D800 〜 U+DFFF)
使用禁止
┏━━━━━━━━━━━━┓
┃00000 xxxx yyyyyy zzzzzz┃
┗━━━━━━━━━━━━┛
│ 5bit|4bit| 6bit | 6bit │
1110 xxxx 10yy yyyy 10zz zzzz
(0x80〜0xBF)
-
(0xE0) (0xA0〜0xBF)
(0xE1〜0xEC) (0x80〜0xBF)
(0xED) (0x80〜0x9F)
(0xEE〜0xEF) (0x80〜0xBF)
UTF8-4
1〜16面 (補助文字)
U+010000 〜 U+10FFFF
(x xxyy yyyy zzzz zzww wwww)
┏━━━━━━━━━━━━┓
┃xxx yyyyyy zzzzzz wwwwww┃
┗━━━━━━━━━━━━┛
│3bit| 6bit| 6bit | 6bit │
1111 0xxx 10yy yyyy 10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
(0xF0) (0x90〜0xBF)
(0xF1〜0xF3) (0x80〜0xBF)
(0xF4) (0x80〜0x8F)

■注意

バイト値による判別方法


5.2 旧 UTF-8 (RFC2279(廃),1998年1月)

RFC2279 は廃止されたが,次の理由でここに掲載する.


UCS-4 第1バイト 第2バイト 第3バイト 第4バイト 第5バイト 第6バイト
0群0面0区0〜127点 (ASCII)
0000 0000 0xxx xxxx
(U+0000 〜 U+007F)
0xxx xxxx
(0x00〜0x7F)
- - - - -
0群0面0区128点〜7区
0000 0xxx xxyy yyyy
(U+0080 〜 U+07FF)
110x xxxx
(0xC0〜0xDF)
10yy yyyy
(0x80〜0xBF)
- - - -
0群0面8〜255区
xxxx yyyy yyzz zzzz
(U+0800 〜 U+FFFF)
1110 xxxx
(0xE0〜0xEF)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
- - -
0群1〜31面
0000 0000 000x xxyy
yyyy zzzz zzww wwww
1111 0xxx
(0xF0〜0xF7)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
- -
0群32面〜3群
0000 00xx yyyy yyzz
zzzz wwww wwvv vvvv
1111 10xx
(0xF8〜0xFB)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
10vv vvvv
(0x80〜0xBF)
-
4群〜127群
0xyy yyyy zzzz zzww
wwww vvvv vvuu uuuu
1111 110x
(0xFC〜0xFD)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
10vv vvvv
(0x80〜0xBF)
10uu uuuu
(0x80〜0xBF)

バイト値による判別方法


6.Unicode 関数・マクロ集 (C言語)

実験中なので,仕様は予告なく変更する可能性があります.

6.1 UTF-16 符号単位がサロゲートか否かを判定する.

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
// C99 以後
#include <stdint.h>
#elif defined(__unix__)
#include <sys/types.h>
#else
// 処理系に応じて変更すること.
typedef unsigned char  uint8_t;     // 8ビット無符号整数
typedef unsigned short uint16_t;    // 16ビット無符号整数
typedef unsigned int   uint32_t;    // 32ビット無符号整数
#endif

typedef uint8_t  utf8_t;    // UTF-8  の符号単位
typedef uint16_t utf16_t;   // UTF-16 の符号単位
typedef uint32_t utf32_t;   // UTF-32 の符号単位
typedef uint32_t unicode_t; // Unicode スカラ値
typedef uint32_t ucs4_t;    // UCS-4 の1文字

typedef int Bool;

#define OK   0
#define FAIL (-1)

// Unicode スカラ値の範囲
#define UNICODE_MIN     ((unicode_t)0x000000)
#define UNICODE_MAX     ((unicode_t)0x10FFFF)

// 無効な Unicode スカラ値
// (注意:規格とは無関係に,このページで独自に定義した値.)
#define UNICODE_EOF     (~(unicode_t)0)

// 補助面の最初の符号位置 (1面の最初の符号位置)
#define UNICODE_PLANE1_MIN   ((unicode_t)0x010000)

// UTF-16 符号単位の範囲
#define UTF16_MIN       ((utf16_t)UNICODE_MIN)
#define UTF16_MAX       ((utf16_t)0xFFFF)

// UCS-4 の範囲
#define UCS4_MIN        ((ucs4_t)0x00000000)
#define UCS4_MAX        ((ucs4_t)0x7FFFFFFF)

/*─────────────────────────────────────
説明  :サロゲート領域の定義.
2007/08/12(日) 作成
2007/10/20(土) SURROGATE_BITS,{HIGH,LOW,BOTH}_SURROGATE_MASK を追加.
─────────────────────────────────────*/
#define HIGH_SURROGATE_MIN ((utf16_t)0xD800) // 上位サロゲート領域開始位置
#define HIGH_SURROGATE_MAX ((utf16_t)0xDBFF) // 上位サロゲート領域終了位置
#define LOW_SURROGATE_MIN  ((utf16_t)0xDC00) // 下位サロゲート領域開始位置
#define LOW_SURROGATE_MAX  ((utf16_t)0xDFFF) // 下位サロゲート領域終了位置
#define SURROGATE_MIN       HIGH_SURROGATE_MIN  // サロゲート領域開始位置
#define SURROGATE_MAX       LOW_SURROGATE_MAX   // サロゲート領域終了位置

// サロゲート可変部分のビット数 (上位,下位共通)
#define SURROGATE_BITS     10

// サロゲート符号単位の可変部分を抽出するためのマスク
#define HIGH_SURROGATE_MASK (((utf16_t)1 << SURROGATE_BITS) - 1)       // 上位用
#define LOW_SURROGATE_MASK  (((utf16_t)1 << SURROGATE_BITS) - 1)       // 下位用
#define BOTH_SURROGATE_MASK (((utf16_t)1 << (SURROGATE_BITS + 1)) - 1) // 上下用

/*─────────────────────────────────────
機能  :有効なスカラ値か否かを判定する.
入力  :unicode:スカラ値.
戻り値:unicode が有効なスカラ値のときそのときに限り真.
2009/01/23(金) 作成
─────────────────────────────────────*/
#define IsValidUnicode(unicode) \
  (((unicode) <= UNICODE_MAX) && !IsSurrogate(unicode))

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が上位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が上位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsHighSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)HIGH_SURROGATE_MASK) \
   == (unicode_t)HIGH_SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が下位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が下位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsLowSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)LOW_SURROGATE_MASK) \
   == (unicode_t)LOW_SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が上位または下位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が上位または下位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)BOTH_SURROGATE_MASK) \
   == (unicode_t)SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の2つの符号単位の組 (first,second) がサロゲート・ペアか否か
        を判定する.
入力  :first,second:UTF-16 の符号単位.first が先,second が後.
戻り値:(first,second) がサロゲート・ペアのときそのときに限り真.
2007/08/12(日) 作成
─────────────────────────────────────*/
#define IsSurrogatePair(first, second) \
  (IsHighSurrogate(first) && IsLowSurrogate(second))

/*─────────────────────────────────────
機能  :IsHighSurrogate(),IsLowSurrogate(),IsSurrogate() のテスト.
        すべての UTF-16 符号単位についてこれらの述語の判定結果が正しいことを
        確認するとともに,真理値表を out に出力する.
2007/08/12(日) 作成
2007/08/16(木) 改名 (SurrogateTest() ⇒ IsSurrogateTest())
2007/10/20(土) 改定
─────────────────────────────────────*/
void IsSurrogateTest(FILE *out)
{
  unicode_t c;
  Bool isHighSurrogate, isLowSurrogate, isSurrogate;

  fprintf(out, "UTF-16\tHigh\tLow\tSurrogate\n");
  for(c = UTF16_MIN;  c <= UTF16_MAX;  c++) {
    // Is{High,Low,}Surrogate() と別の方法で判定する.
    isHighSurrogate = ValueInRangeInclusive(HIGH_SURROGATE_MIN, c, HIGH_SURROGATE_MAX);
    isLowSurrogate = ValueInRangeInclusive(LOW_SURROGATE_MIN, c, LOW_SURROGATE_MAX);
    isSurrogate = ValueInRangeInclusive(SURROGATE_MIN, c, SURROGATE_MAX);

    // 異なる判定方法の結果が一致することを確認する.
    assert(IsHighSurrogate(c) == isHighSurrogate);
    assert(IsLowSurrogate(c) == isLowSurrogate);
    assert(IsSurrogate(c) == isSurrogate);

    // 判定結果 (真理値表) を出力する.
    fprintf(out, "%04X\t%s\t%s\t%s\n", c,
            isHighSurrogate ? "*" : "",
            isLowSurrogate ? "*" : "",
            isSurrogate ? "*" : "");
  }
}

/*─────────────────────────────────────
テスト用
─────────────────────────────────────*/
int main(void)
{
  IsSurrogateTest(stdout);
  return EXIT_SUCCESS;
}

6.2 サロゲート・ペア ⇔ Unicode スカラ値変換

/*─────────────────────────────────────
機能  :サロゲート・ペアを解読 (Unicode スカラ値に変換) する.
入力  :(1) high:上位サロゲート符号単位 (0xD800 〜 0xDBFF).
        (2) low :下位サロゲート符号単位 (0xDC00 〜 0xDFFF).
戻り値:サロゲート・ペアに対応する Unicode スカラ値 (U+010000 〜 U+10FFFF).
2007/08/12(日) 作成
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
unicode_t DecodeSurrogatePair(unicode_t high, unicode_t low)
{
  return ((high & (unicode_t)HIGH_SURROGATE_MASK) << SURROGATE_BITS) +
         (low & (unicode_t)LOW_SURROGATE_MASK) + UNICODE_PLANE1_MIN;
}

/*─────────────────────────────────────
機能  :Unicode スカラ値 (1〜16面) を上位サロゲートに変換する.
入力  :unicode:Unicode スカラ値.1〜16面 (U+010000 〜 U+10FFFF) でなければ
        ならない.
戻り値:unicode に対応する上位サロゲート.
2007/08/16(木) 作成
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
#define Unicode_ToHighSurrogate(unicode) \
  ((utf16_t)(((((unicode) - UNICODE_PLANE1_MIN) >> SURROGATE_BITS) \
              & HIGH_SURROGATE_MASK) | HIGH_SURROGATE_MIN))

/*─────────────────────────────────────
機能  :Unicode スカラ値 (1〜16面) を下位サロゲートに変換する.
入力  :unicode:Unicode スカラ値.1〜16面 (U+010000 〜 U+10FFFF) でなければ
        ならない.
戻り値:unicode に対応する下位サロゲート.
2007/08/16(木) 作成
2007/08/28(火) 改定 (無駄を省いて簡略化)
2007/10/20(土) 改定
─────────────────────────────────────*/
#define Unicode_ToLowSurrogate(unicode) \
  ((utf16_t)((unicode) & LOW_SURROGATE_MASK | LOW_SURROGATE_MIN))

/*─────────────────────────────────────
機能  :Unicode_ToHighSurrogate(),Unicode_ToLowSurrogate(),
        DecodeSurrogatePair() のテスト.
        ・すべてのサロゲート・ペアについて,DecodeSurrogatePair(),
          Unicode_To{High,Low}Surrogate() の結果が正しいことを確認する.
        ・サロゲート・ペアと Unicode スカラ値の対応表を out に出力する.
2007/08/12(日) 作成 (DecodeSurrogatePair() のテスト.)
2007/08/16(木) Unicode_To{High,Low}Surrogate() のテストを追加.
2007/08/18(土) 改名 (DecodeSurrogatePairTest() ⇒ SurrogatePairConvTest())
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
void SurrogatePairConvTest(FILE *out)
{
  unicode_t high, low;         // サロゲート・ペア
  unicode_t high2, low2;       // Unicode_To{High,Low}Surrogate() の結果.
  unicode_t value;             // DecodeSurrogatePair() の変換結果
  // 第1〜16面の符号位置を順番に.
  unicode_t refval = UNICODE_PLANE1_MIN;

  fprintf(out, "High Low  Unicode\n"
               "---- ---- --------\n");
  for(high = HIGH_SURROGATE_MIN;  high <= HIGH_SURROGATE_MAX;  high++) {
    for(low = LOW_SURROGATE_MIN;  low <= LOW_SURROGATE_MAX;  low++, refval++) {
      assert(IsSurrogatePair(high, low));
      value = DecodeSurrogatePair(high, low);
      high2 = Unicode_ToHighSurrogate(value);
      low2 = Unicode_ToLowSurrogate(value);
      fprintf(out, "%04X %04X U+%06lX\n", high, low, value);
      assert(value == refval);
      assert(high2 == high);
      assert(low2 == low);
    }
  }
  assert(refval == UNICODE_MAX + 1);
}

/*─────────────────────────────────────
テスト用
─────────────────────────────────────*/
int main(void)
{
  SurrogatePairConvTest(stdout);
  return EXIT_SUCCESS;
}

6.3 UTF-16 文字列関数

/*─────────────────────────────────────
機能  :Unicode スカラ値 ⇒ UTF-16 変換 (UTF-16 文字列バッファに1文字追記).
        Unicode スカラ値 unicode を UTF-16 文字列バッファに追記する.
入出力:*pWritePtr:UTF-16 文字列バッファの,次の書き込み位置を指すポインタ.
        書き込み成功ならば,次の書き込み位置を指すように更新される.
入力  :(1) bufEnd:UTF-16 文字列バッファの直後のアドレス.つまり UTF-16 バ
            ッファ がN要素の配列 buf[N] ならば &buf[N].ただし UTF-16 文字
            列を必ず NUL 終端したい場合は &buf[N-1] とする (NUL は自分で書き
            込むこと).
        (2) unicode:Unicode スカラ値.
戻り値:UTF-16 バッファに書き込まれた UTF-16 符号単位の数 (1〜2).
        バッファの空き容量不足で書き込めなかった場合には0.
        unicode が範囲外 (>U+10FFFF) の場合は -1.
2007/08/16(木) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
int UTF16_PutChar(utf16_t **pWritePtr, utf16_t *bufEnd, unicode_t unicode)
{
  utf16_t *dest = *pWritePtr;   // 書き込み位置

  if(unicode <= UTF16_MAX) {
    // unicode が0面の場合:そのまま書き込む.
    if(dest >= bufEnd) goto BufferFull;
    *dest = (utf16_t)unicode;
    *pWritePtr = dest + 1; // 次の書き込み位置
    return 1;
  } else if(unicode <= UNICODE_MAX) {
    // unicode が1〜16面の場合:サロゲート・ペアに変換して書き込む.
    if(dest + 1 >= bufEnd) goto BufferFull;
    dest[0] = Unicode_ToHighSurrogate(unicode);
    dest[1] = Unicode_ToLowSurrogate(unicode);
    *pWritePtr = dest + 2;  // 次の書き込み位置
    return 2;
  } else {
    // unicode が17面以上の場合:エラー.
    return -1;
  }
BufferFull: return 0;
}

/*─────────────────────────────────────
機能  :UTF-16 ⇒ Unicode スカラ値変換
        UTF-16 文字列から1文字読み取り,Unicode スカラ値に変換する.
        文字列は NUL 終端でも,そうでなくてもよい.
入力  :stringEnd:UTF-16 文字列の終端を指定する.
        (1) 非 NUL 終端文字列の場合:文字列の直後 (最後の符号単位の次のアド
            レス) を指す.
        (2) NUL 終端文字列の場合:NULL.
入出力:*pReadPtr:UTF-16 文字列の読み出し位置を指すポインタ.1文字を読み
        出した後,次の読み出し位置に更新される.ただし文字列が NUL 終端
        (stringEnd=NULL) で,既に NUL を指している場合には更新されない.
戻り値:*pReadPtr が指す1文字.ただし,
        ・NUL 終端文字列で,*pReadPtr が終端 NUL を指している場合は NUL を
          返し,読み出し位置は更新しない.
        ・非 NUL 終端文字列 (stringEnd≠NULL) で,既に終端に達している場合
          (*pReadPtr≧stringEnd) は UNICODE_EOF を返し,読み出し位置は更新
          しない.
        ・**pReadPtr が孤立した ((正しい) ペアでない) サロゲートの場合は,
          それをそのまま返す.読み出し位置は次の符号単位に進む.
2007/08/17(金) 作成
─────────────────────────────────────*/
unicode_t UTF16_GetChar(const utf16_t **pReadPtr, const utf16_t *stringEnd)
{
  const utf16_t *src = *pReadPtr;
  unicode_t uc, uc2;

  if(stringEnd == NULL) {
    // NUL 終端文字列の場合
    if((uc = *src++) != 0) {
      if(IsHighSurrogate(uc)) {
        uc2 = *src;
        if(IsLowSurrogate(uc2)) {
          uc = DecodeSurrogatePair(uc, uc2);
          src++;
        }
      }
      *pReadPtr = src;
    }
  } else {
    // 非 NUL 終端文字列の場合
    if(src >= stringEnd) {
      uc = UNICODE_EOF; // 文字列終端
    } else {
      uc = *src++;
      if(IsHighSurrogate(uc) && (src < stringEnd)) {
        uc2 = *src;
        if(IsLowSurrogate(uc2)) {
          uc = DecodeSurrogatePair(uc, uc2);
          src++;
        }
      }
      *pReadPtr = src;
    }
  }
  return uc;
}

/*─────────────────────────────────────
機能  :UTF-16 文字列の長さ (文字数および符号単位数) を取得する.
入力  :(1) string:UTF-16 文字列.NUL 終端でも,そうでなくてもよい.
        (2) stringEnd:string の終端.
            ・非 NUL 終端文字列の場合:string の最後の符号単位の次のアドレス.
            ・NUL 終端文字列の場合:NULL.
出力  :*pNUnits:pNUnits≠NULL ならば,string の UTF-16 符号単位の個数を返す.
        終端 NUL は含まない.
戻り値:string に含まれる文字数.ペアをなさないサロゲートも1文字と数える.
        (UTF16_GetChar() が返す文字 (終端 NUL を除く) の個数と一致.)
2007/08/18(土) 作成
─────────────────────────────────────*/
size_t UTF16_Length(const utf16_t *string, const utf16_t *stringEnd, size_t *pNUnits)
{
  const utf16_t *p = string;
  size_t nChars = 0;
  unicode_t uc;

  if(stringEnd == NULL) {
    // NUL 終端文字列の場合
    while((uc = *p) != 0) {
      p++;
      nChars++;
      if(IsHighSurrogate(uc) && IsLowSurrogate(*p)) p++;
    }
  } else {
    // 非 NUL 終端文字列の場合
    while(p < stringEnd) {
      nChars++;
      uc = *p++;
      if(IsHighSurrogate(uc) && (p < stringEnd) && IsLowSurrogate(*p)) p++;
    }
  }
  if(pNUnits != NULL) *pNUnits = (size_t)(p - string);
  return nChars;
}

/*─────────────────────────────────────
機能  :UTF16_GetChar(),UTF16_PutChar(),UTF16_Length() のテスト.
        エラー処理は手抜き.
        (1) UTF-16 文字列 string を NUL 終端文字列として buf[] にコピーする
            (Unicode スカラ値経由) とともに,文字数および UTF-16 符号単位数
            を数える.
        (2) UTF-16 文字列 string を非 NUL 終端文字列として buf[] にコピーす
            る (Unicode スカラ値経由) とともに,文字数および UTF-16 符号単位
            数を数える.また UTF-32 ファイル UTF-32.txt に書き出す.
        (3) buf[] を UTF-16 ファイル UTF-16.txt に書き出す.
2007/08/18(土) 作成
─────────────────────────────────────*/
void UTF16_Test(void)
{
  // Windows では wchar_t=UTF-16 なので OK だが,他の OS ではダメかも.
  static const utf16_t string[] =
    L"\xFEFF"   // BOM
    L"\x24B6牛丼一筋80年♪ \xD842\xDFB7野家\n" // サロゲート・ペア ([土]+[口])
    L"\x24B7森\x9DD7外\n"   // Windows-31J に存在しない文字 // [區鳥]
    L"\x24B8草\x5F45剛\n"   // Windows-31J 機種依存文字 // [弓剪]
    L"\x24B9\x9AD9知新聞\n" // Windows-31J 機種依存文字 // (はしご高)
    L"\x24BA虹は♂,\x873Aは♀.\n" // Windows-31J に存在しない文字 // [虫兒]
    L"\x2707\x21D6\x21D7\x21D8\x21D9\n"; // Unicode 記号
  utf16_t buf[64];
  const utf16_t *src = string;
  const utf16_t * const stringEnd = string + wcslen(string);
  utf16_t *dest = buf;
  utf16_t * const bufEnd = ArrayEnd(buf);
  unicode_t uc;
  size_t size, nChars, nUnits;
  int result;
  FILE *out;

  // NUL 終端文字列としてコピーしてみる.
  for(nChars = 0;  ;  nChars++) {
    uc = UTF16_GetChar(&src, NULL);
    result = UTF16_PutChar(&dest, bufEnd, uc);
    printf("%06X %d\n", uc, result);
    if((uc == 0) || (result <= 0)) break;
  }
  if(result > 0) {
    assert(wcscmp(string, buf) == 0);
    assert(nChars == UTF16_Length(string, NULL, &nUnits));
    assert(nUnits == wcslen(string));
    printf("%lu characters, %lu units\n",
           (unsigned long)nChars, (unsigned long)nUnits);
  }

  printf("\n");

  // 非 NUL 終端文字列としてコピーしてみる.
  // UTF-32 のファイルも作ってみる.
  out = fopen("UTF-32.txt", "wb");
  assert(out != NULL);
  src = string;
  dest = buf;
  nChars = 0;
  while((uc = UTF16_GetChar(&src, stringEnd)) != UNICODE_EOF) {
    result = UTF16_PutChar(&dest, bufEnd, uc);
    printf("%06X %d\n", uc, result);
    if(result <= 0) break;
    size = fwrite(&uc, 1, sizeof(uc), out);
    assert(size == sizeof(uc));
    nChars++;
  }
  fclose(out);
  if(result > 0) {
    assert(memcmp(string, buf, sizeof(utf16_t) * (size_t)(dest - buf)) == 0);
    assert(nChars == UTF16_Length(string, stringEnd, &nUnits));
    assert(nUnits == (size_t)(src - string));
    printf("%lu characters, %lu units\n",
           (unsigned long)nChars, (unsigned long)nUnits);
  }

  // UTF-16 のファイルも作ってみる.
  out = fopen("UTF-16.txt", "wb");
  assert(out != NULL);
  size = fwrite(buf, sizeof(buf[0]), (size_t)(dest - buf), out);
  assert(size == (size_t)(dest - buf));
  fclose(out);
}

/*─────────────────────────────────────
テスト用.
─────────────────────────────────────*/
int main(void)
{
  UTF16_Test();
  return EXIT_SUCCESS;
}

6.4 UTF-8/CESU-8 文字列関数・マクロ

// UTF-8/CESU-8 1文字の最大バイト数
#define UTF8_RFC3629_MAXBYTES   4
#define UTF8_RFC2279_MAXBYTES   6
#define UTF8_MAXBYTES            UTF8_RFC3629_MAXBYTES
#define CESU8_MAXBYTES           6

/*─────────────────────────────────────
入力  :n:1 〜 UTF8_RFC2279_MAXBYTES.
戻り値:UTF-8 のnバイト文字に含まれるスカラ値のビット数.
2008/12/28(日) 作成
─────────────────────────────────────*/
#define UTF8_BITS(n)    (((n) == 1) ? 7 : (n) * 5 + 1)

/*─────────────────────────────────────
入力  :n:1 〜 UTF8_RFC2279_MAXBYTES.
戻り値:UTF-8 でnバイト文字として表現可能な最大の Unicode スカラ値.
注意  :n=RFC3629_MAXBYTES の場合,0x10FFFF (RFC3629 での最大値) ではなく,
        0x1FFFFF (RFC2279 での最大値) を返す.
用途  :UTF-8 非最短形式の効率的判定.
2008/12/28(日) 作成
─────────────────────────────────────*/
#define UTF8_MAX(n)     ((unicode_t)(((unicode_t)1 << UTF8_BITS(n)) - 1U))

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 のnバイト文字 (UTF8-n) の先頭バイトのときそのときに
        限り,IS_UTF8_<n>(byte) は真を返す.
注意  :非最短形式の先頭バイト値 (0xC0〜0xC1,0xF5〜0xF7) を排除していない.
余談  :IS_UTF8_[2-6]() については,ValueInRangeInclusive(0xC0, (byte), 0xDF)
        などとするのに比べて条件分岐が1回少ないのでちょっと速い(はず).
2009/02/10(火) 作成
─────────────────────────────────────*/
#define IS_UTF8_1(byte)      ((byte) <= 0x7FU)
#define IS_UTF8_2(byte)      (((byte) & 0xE0U) == 0xC0U)
#define IS_UTF8_3(byte)      (((byte) & 0xF0U) == 0xE0U)
#define IS_UTF8_4(byte)      (((byte) & 0xF8U) == 0xF0U)
#define IS_UTF8_5(byte)      (((byte) & 0xFCU) == 0xF8U) // RFC2279 のみ
#define IS_UTF8_6(byte)      (((byte) & 0xFEU) == 0xFCU) // RFC2279 のみ

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 の複数バイト文字の第2バイト以後 (UTF8-tail) のとき
        そのときに限り真.
2009/02/10(火) 作成
─────────────────────────────────────*/
#define IS_UTF8_TAIL(byte)   (((byte) & 0xC0U) == 0x80U)

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 の先頭バイトのときそのときに限り真.
注意  :非最短形式の先頭バイト値 (0xC0〜0xC1,0xF5〜0xF7) を排除していない.
2009/02/10(火) 作成
2010/03/18(木) 高速化のため判定順序を入れ替え.
─────────────────────────────────────*/
#define IS_UTF8_RFC3629_HEAD(byte) \
  (!IS_UTF8_TAIL(byte) && ((byte) <= 0xF7U))

#define IS_UTF8_RFC2279_HEAD(byte) \
  (!IS_UTF8_TAIL(byte) && ((byte) <= 0xFDU))

#define IS_UTF8_HEAD(byte)            IS_UTF8_RFC3629_HEAD(byte)

7.文字コード雑記

7.1 謎の検索ワード集 (文字コード一般)

「UTF-8 ASCII 変換」

あのー,UTF-8 がどういう性格の文字コード (符号化方式) か,ちょっとでも調べてみたことあります?

UTF-8 は ASCII の完全上位互換になるように決められた符号化方式だから,

  • ASCII 文字列はそのままで UTF-8 文字列だから変換する必要がない.
  • ASCII 文字だけからなる UTF-8 文字列は ASCII 文字列として使えるから変換する必要がない.
  • 非 ASCII 文字を含む UTF-8 文字列は ASCII に変換できない.(当然)

つまり変換する必要がないか,変換できないのどちらかしかないから, 「変換方法」を探しても見つかるわけがない.

闇雲に「変換方法」を探す前に, どういう文字コードなのかを少しは理解していないと,象の卵 (あるはずのないもの) をいつまでも探し続けるはめになりますよ.

2013/04/06(土) 作成
「UTF-16 終端文字」, 「UTF-8 終端文字」, 「SJIS 終端文字」など

時々こういうキーワードで検索して来る人がいるが, 文字コードの規格で終端文字というものが決まっているわけではない 「終端文字として何を使うか」ではなく,「終端文字というものを使う」ことさえ規定されていない. テキストファイルに終端文字なんてないでしょ? えっ,それも知らないの?  そういう人は, 時々テキストファイルをバイナリエディタで開いて見る習慣をつけると文字コードや文字列の正体が理解しやすくなると思うよ.

0 (NUL) をメモリ内の文字列の終端文字として使用するのはC言語 (およびその派生言語) の仕様. C言語が普及する以前は,終端文字というものを使わない文字列の方が主流だった.

正確に言うと,C言語であっても,すべての文字列が NUL 終端とは限らない. 文字列の 終端方法   (と書くと文字列には必ず終端文字があると誤解する人がいそうだから 「長さの定義方法」に訂正)API やライブラリの仕様なので, それぞれのマニュアルでちゃんと確認すること. API によっては,終端文字のない文字列を使用する. 例えば Windows の文字コード変換 API である MultiByteToWideChar( ) や WideCharToMultiByte( ) などは,文字列は NUL 終端でもそうでなくても使用できる.一般論で「(エンコーディング名) 終端文字」なんて検索しても正解が見つかるわけがない.

ところで文字列 (の本体) は「符号単位の配列」だということは理解してる?
「文字列には必ず終端文字がある」という固定観念に凝り固まっている人は,配列の使い方を勉強し直した方がいい. 配列の長さ (要素数) を指定・取得するにはどんな方法があると思う?
(ASCII などの1バイト文字列やマルチバイト文字列 (シフトJIS,EUC-*,UTF-8 など) の符号単位は1バイトだが,UTF-16 は2バイト,UTF-32 では4バイト. もちろん NUL も一つの符号単位で表されるので,1バイトとは限らない.)


■余談

  • NUL 終端文字列 (NUL-terminated string) は「C文字列 (C string)」,「ASCIZ (または ASCIIZ) 文字列」(ASCII+Zero の略) などともいう.

    Wikipedia 英語版の "Null-terminated string" によると,C言語が登場するより前に NUL 終端文字列を使っていたのは PDP-10 (1966年生産開始(?)の36ビットメインフレーム) と PDP-11 (1970年出荷開始の16ビットミニコン) のアセンブリ言語らしい.

    当時は現在に比べて桁違いにメモリ容量が少なく高価だったので, メモリ使用量を節約しつつ任意の長さの文字列を扱えるように NUL 終端文字列が考案されたようである. (ただし長い文字列の長さを調べるのに時間がかかる.)

  • CP/M およびそれを真似た DOS では,文字列を出力する (アセンブラ) API (INT 21H,AH=09H) に渡す文字列は '$' 終端文字列だった. 米国製 OS だというのに,これじゃ金額を出力できないじゃないか. ひどすぎる仕様だ.(笑)
    (実際には,文字列は別の API で出力していたと思う.)

  • ASCII 制御文字には ETX (End Of Text:テキスト終了,0x03) というのがあるが, これは伝送制御文字なので,装置間の通信メッセージでしか用いられないし, 必ず使用しなければならないというものでもない (プロトコルに依存する).

  • ETX は CTRL-C のことだが,ASCII 本来の意味は現在の PC での用法とは全く関係ない. これに対し UNIX/Linux では,コンソールからの入力を終了させる EOF 文字として CTRL-D が用いられているが,これは EOT (End Of Transmission:伝送終了,0x04) なので ASCII 本来の使い方である. (UNIX 開発当時,コンピュータと端末は別々の装置だった.)


■参考

2009/12/13(日) 作成
2010/01/10(日) 改定
2010/03/03(水) 改定
2010/03/05(金) 改定
2010/08/08(日) 追記
2011/01/19(水) 追記
2011/02/11(金) 改定
「EOF 文字コード」, 「SJIS EOF」, 「UTF-8 EOF」, 「UTF-16 EOF」など

なぜか最近,こういうキーワードで検索して来る人が多い気がする. 以前は気になるほど多くなかったと思うんだけど.
どうやら EOF が文字コードだと思っている人が増えたらしい.

念のために聞くけど,"EOF" の意味は知ってますよね?

End-Of-File … ファイルを読もうとしても,既にファイルの最後まで読んでしまっているので, それ以上読み取れるデータがない状態
じゃあ質問するけど,

  • 文字コードによって EOF が異なるとしたら,事前にテキストファイルの文字コードがわからない場合は どうやってファイルの終端を判定するの?  判定を誤ると,次の文字を読もうと待ち続けてハングアップしますよ.
  • 文字を全く含まないバイナリファイルを読むときはどうするの?
  • 複数の文字コードが混在するファイルの場合は?
  • たぶん「EOF 文字」というものがファイルの最後に書かれていると信じてるんだろうけど,空 (0バイト) のファイルを読もうとしたらどうなるの? ハングアップするの? 死ぬの?

どれも難しい問題に思う人がいるかもしれないが,こういう問題は存在しない. なぜなら,EOF は文字コードとは全く無関係だから.EOF は文字列や文字コードの概念ではなく,ファイルシステム API (ファイル入出力関数) の概念なので,EOF の判定方法についてはそれらのマニュアルをちゃんと読むこと. 例えばC標準ライブラリ関数 fgetc() などが返す EOF という値はエラー値であって文字コードではない. (EOF の具体的な値を知りたければ自分で stdio.h を見て確認すること. 文字コードでは絶対あり得ない値であることがわかるだろう.)


●いわゆる「EOF 文字 (CTRL-Z=0x1A)」について

「そんなこと言うけど,EOF 文字 (0x1A=CTRL-Z) というものが実際にあるじゃないか」と言いたい人もいるだろう. 確かにそうだけど,これははっきり言って大昔の遺物だし, しかも一部のシステムで使われていただけなので今時こんなものを使うべきではない. 特別な事情 (後述) がないのに使えば「嘲笑とクレームの的」になることを覚悟してください.


●EOF 文字の起源と誤解の歴史 (一部推測 (たぶん間違ってはいない))

大昔 (1980年代) の CP/MMS-DOS 時代のテキストファイルの一部は終端文字としてこの「EOF 文字」を使っていたけど,これは文字コードの規格とは全く関係がなく,OS (CP/M) の制約に由来する仕様.

CP/M ではファイルを128バイトのブロック (フロッピーディスクの1セクタ) 単位でしか管理できなかったため,テキストファイルでは最後のブロックの余った領域 (1〜127バイト) を全部 0x1A で埋めていた.したがってテキストのサイズが丁度128バイトの倍数ならば,0x1A は存在しない.このため CP/M の 0x1A は EOF 文字ではなくフィラーという方が正しいし,文字コードではないので ASCII の SUB (=0x1A) とは何の関係もない

○ちょっと修正 (2013/03/29(金))

上に書いたとおり ASCII の SUB に EOF の意味はないが, (通信エラーや文字コード変換エラーなどにより発生した) 無効な文字コードを置き換える (SUBstitute) ための制御文字なので,CP/M のテキストファイルでは余白を埋めるためにこの「有効な文字ではないもの」 を使ったのかもしれない.(推測)

参考:JIS X 0201 での SUB の定義
(手元にある「JISハンドブック 情報処理 ソフトウェア・符号編 (1994)」から)

SUB:置換キャラクタ (Substitute Character)
無効又は誤りとなったキャラクタを自動的に置き換えるのに用いる置換用の特殊機能キャラクタ.

8.3 制御機能の定義

8.3.144 SUB 置換 (SUBSTITUTE)

SUB は,不当又は誤りと判明した文字に置き換えて使う. SUB への置き換えは,自動的な手段で起こる.

MS-DOS はファイルを1バイト単位で管理できるので CP/M ファイルのようなフィラーは本来不要なのだが,CP/M 用のソフト (メジャーな英文ワープロソフト WordStar など) が MS-DOS に移植されたため,CP/M で作成されたファイルがそのまま MS-DOS でも使われることが多かった. MS-DOS は CP/M のフロッピーディスクを読むことができたので,テキストファイル末尾の 0x1A に対処しなければならなかったのである.

そして CP/M テキストファイルが「テキストファイルの最後には1バイトの 0x1A を付ける」という仕様に誤解されて広まったのが EOF 文字の由来だと思う.
(CP/M テキストファイルを読み込むアプリケーションの開発者が「最初の 0x1A を読んだ時点で EOF と判定する」ように実装したロジックが,そのままファイルの仕様だと誤解されたのだろう. もっとも上記のとおり,実際の CP/M ファイルでは 0x1A が存在しない場合もあるので, この判定条件だけでは不十分である.)


●現在でも EOF 文字を使わざるを得ない場合

もし1980年代に MS-DOS で開発されたシステムや, それらのファイルを引き継いだシステムがいまだにどこかで動いているのなら, それらとの間で受け渡すファイルには EOF 文字を使わざるを得ないだろう.
(実例 → 【C#】EOFの文字コード(制御文字?)ってありますか? (@IT会議室))

しかしそんな必要性もないのに今更 EOF 文字なんか使おうものなら, 嘲笑とクレームの的になるだろう.

もっとも,現在でもこの EOF 文字が使われているところがある.それはファイルではなく, Windows のコマンドプロンプトでのキー入力である.(GUI ではない) コンソールアプリの入力はキーボードだけなので, キー入力が終了したこともキー入力で知らせる必要がある. CTRL-Z を押すと,OS または標準Cライブラリのどちらか (ちゃんと調べてない) が入力終了と判断し, Cの標準入出力ライブラリはコンソールアプリに対して EOF に達したことを知らせる.
アプリに通知されるのは EOF (という状態) であって,入力された文字コード (CTRL-Z=0x1A) ではない
なおに書いたとおり,UNIX/Linux では CTRL-Z ではなく CTRL-D (ASCII EOT) が用いられる.


■参考

2013/03/10(日) 作成
2013/03/29(金) 少し修正
2015/02/09(月) 少し追記
2016/04/09(土) わずかに追記

7.2 ASCII の NUL と DEL の本来の意味

ASCII の NUL の符号位置は 0x00,DEL は 0x7F であるが, そうなっている理由には元々物理的必然性があった.

ASCII が制定された当時,デジタル記録媒体として紙テープが使われていた (1980年代初め頃まで). 紙テープは,穴が空いている状態が '1' を,穴が空いていない状態が '0' を表す. したがって紙テープの未使用の (まだ文字が書き込まれていない) 部分は 0x00 である.これを空文字 (null,ASCII の文字名は NUL) と定義した.

紙テープ上に記録された文字を削除したい場合には, 専用の器具を使ってすべてのビット位置に穴を空ける. つまり 0x7F であり,これを削除済 (DELeted) と定義した.

2015/02/11(水) 追記

テレタイプのキーボードには DEL または RUBOUT (削除,抹消) というキーがあり,これを押すと紙テープに DEL が書き込まれたようである.

■参考

現在では DEL は,(文字などを) 削除するコマンドとして使われることが多いが, 本来は削除コマンドではなく文字が削除された痕跡であった. DEL を削除コマンドとして使用するのは,単にその文字名を流用しているだけであり, ASCII 本来の意味とは関係ない.

まとめると,

どちらの場合も,読むべき文字が存在していないのだから, 紙テープ読み込み時には無視 (読み飛ば) すべき文字コードだったのである.
しかしこれらの本来の意味は,紙テープとともに廃れてしまった.

ちなみに空文字を NULL や null と書いてしまうと,プログラミング (特に C/C++/Java 等) の文脈では NULL ポインタと混同するおそれがある (実際そういう人がいる) ので,NULL ではなく NUL と表記する方がいいと思う.(それでも混同する人はいるだろうが.)

2013/03/29(金) 追記

で SUB について触れたが,DEL も SUB も無効な文字を表すという点で似ているといえるかもしれない.DEL の方は人間が (紙テープ上の文字を) 削除した痕跡であるのに対し,SUB の方は自動的に無効と判定された文字の痕跡である.


■参考

2007/08/03(金) 作成
2013/03/29(金) 少し修正
2015/02/10(火)
2015/02/11(水)
少し追記

8.文字コード関連データファイル

文字コード処理に役立つ (と思われる) データファイルを随時掲載します. ただし,筆者の理解不足や誤解により誤ったデータがあるかもしれませんので, ご利用は自己責任で.

データ形式は主として CSV と,Cのプログラムですぐ利用できる (構造体) 配列のソースにする予定.

8.1 シフトJIS / JIS X 0208 文字コード表
   (現在の表示環境におけるシフトJISの2バイト文字集合を表示)

「Shift_JIS (または CP932,Windows-31J) コード表」などで検索してくる人が多いので追加.

現在の表示環境で表示可能なシフトJISの2バイト文字を区点番号表で示す (1バイト文字については JIS X 0201 を参照). 1〜120区を収録してあるので,Shift_JIS (標準的なシフトJIS) が使用する2バイト文字集合である JIS X 0208 (1〜94区) だけでなく, Shift_JIS のベンダ拡張版である Windows-31J (CP932),MacJapanese 等の文字も表示可能.

このファイル自身,シフトJISコードで記述しているので, JIS X 0208 以外の文字がどう表示されるかは, このファイルの表示環境に依存する.Windows 環境では Windows-31J が,Mac では MacJapanese が,UNIX 環境では (たぶん) JIS X 0208 の文字集合だけが表示されるだろう. (ファイル内の機種依存領域の説明は,Windows-31J および NEC PC-9800 のものである.MacJapanese については Wikipedia を参考に記入.)

「シフトJIS / JIS X 0208 文字コード表」というタイトルだが, シフトJISのコード値ではなく区点番号で表示している.理由は,

JIS第1水準漢字 (16〜47区) は読みごと,第2水準漢字 (48〜83区 (1983年以降の追加分を除く)) は部首ごとに区点番号表を分けている.

注意:このファイルを別の文字コードに変換する場合, JIS X 0208 で定義されている文字以外は変換条件に応じた文字だけが表示されたり, 文字化けしたりする可能性がある.

ShiftJisTable.txt ダウンロード (プレーンテキストファイル (シフトJIS),92KB)

8.2 Windows-31J (CP932) 文字コード表

上記「シフトJIS / JIS X 0208 文字コード表」を, Windows API の MultiByteToWideChar() で Unicode に変換したテキストファイル. Unicode なので Windows-31J の機種依存文字も文字コード上は機種依存ではなくなっているが, 表示環境の Unicode フォントがこれらの字形を持っていなければ表示できない点に注意. また MultiByteToWideChar() は Windows-31J の未定義/予約部分を Unicode の私用領域 (Private Use Area) に変換する (詳細は調査中) ので,その表示についても機種依存となる.

Windows-31J の文字集合を調べるだけでなく,MultiByteToWideChar() が Windows-31J (特に未定義/予約部分) をどのように変換するかを調べるためにも使用できる.

Windows-31J.zip ダウンロード (UTF-8 + UTF-16(BE),59KB)

9.サイト内関連ページ


10.外部へのリンク

行き当たりばったりにブックマークした,文字コード関連のリンク集 (未整理,敬称略) です.


11.参考図書

文字コード超研究
文字コード超研究
posted with amazlet at 10.04.13
深沢 千尋
ラトルズ
売り上げランキング: 46194
おすすめ度の平均: 4.5
5 隠れた名著
4 まぁまぁ
4 類似所の中では大変読みやすい書籍
5 面白いです。

著者ブログ




Unicode標準入門

楽天で買う

価格:3,990円(税込、送料別)

Unicode標準入門
Unicode標準入門
posted with amazlet at 10.04.13
トニー グラハム 関口 正裕
翔泳社
売り上げランキング: 210985
おすすめ度の平均: 5.0
5 ISO/IEC 10646
5 Localization、Internationalizationの虎の巻です



プログラマのための文字コード技術入門

楽天で買う

価格:2,709円(税込、送料別)

著者サイト




Unicode Standard, Version 5.0, The (5th Edition)
The Unicode Consortium
Addison-Wesley Professional
売り上げランキング: 77034



CJKV日中韓越情報処理

楽天で買う

価格:13,440円(税込、送料別)

CJKV日中韓越情報処理
CJKV日中韓越情報処理
posted with amazlet on 07.05.07
ケン ランディ Ken Lunde 小松 章 逆井 克己
オライリージャパン (2002/12)
売り上げランキング: 177876
おすすめ度の平均: 4.0
4 分厚い。



文字符号の歴史(欧米と日本編)

楽天で買う

価格:6,300円(税込、送料別)

文字符号の歴史―欧米と日本編
安岡 孝一 安岡 素子
共立出版 (2006/02)
売り上げランキング: 174787



Unicode IVS/IVD入門 ―JIS規格の歴史、異体字問題の解説から、Windows 8での対応方法まで
田丸健三郎 小林龍生
日経BP社
売り上げランキング: 179,480

■関連リンク





JISハンドブック 情報基本
文字コード関連規格 (JIS X 0201020202080209021202130221 (Unicode)) や用語集などを含む,情報処理関連 JIS 規格.




ユニコード漢字情報辞典

三省堂
売り上げランキング: 323656
おすすめ度の平均: 4.0
4 まさに漢字情報辞典

12.更新履歴

  1. 2007/05/06(日) 公開
  2. 2007/05/13(日) 「シフトJISの2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」を追加.
  3. 2007/05/20(日) 「シフトJISの2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」のソースファイル・ダウンロードを追加.
  4. 2007/06/07(木) 下記を現在追加中.
  5. 2007/08/03(金) 「ASCII の NUL と DEL の本来の意味」を追加.
  6. 2007/08/12(日) 「Unicode 関数・マクロ集 (C言語)」を追加.
  7. 2007/08/16(木) 「シフトJIS / JIS X 0208 文字コード表 (現在の表示環境におけるシフトJISの2バイト文字集合を表示)」を追加.
  8. 2007/08/18(土) 「UTF-16 文字列関数」を追加.
  9. 2007/08/25(土) 「シフトJIS 2バイト文字の判定」を追加. (ネットで第1バイトの巧妙な判定方法を見つけたので.)
  10. 2007/08/26(日) 「Windows-31J (CP932) 文字コード表」を追加.
  11. 2007/08/28(火) Unicode_ToLowSurrogate() を改定.
  12. 2007/10/20(土) SURROGATE_BITS,{HIGH,LOW,BOTH}_SURROGATE_MASK を追加. それに伴い,Unicode 関数・マクロの定義を改定.
  13. 2007/10/28(日) IsSjisLeadByte() に Shift_JIS (1〜94区) 限定版を追加.
  14. 2008/06/24(火) コードをわずかに改定.(実質的な変更はなし.)
  15. 2008/12/28(日) UTF8_BITS()UTF8_MAX() を追加.
  16. 2009/01/23(金) IsValidUnicode() を追加.
  17. 2009/02/18(水) IS_UTF_[1-6]()IS_UTF8_{HEAD,TAIL}() を追加.
  18. 2009/03/03(火) 「Shift_JIS と Windows-31J (CP932) の違い」を「シフトJISの2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」から分離.
  19. 2009/07/06(月) 「シフトJISの2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」を「シフトJISの2バイト文字と区点番号の対応表」に入れ替え.
  20. 2009/12/13(日)「終端文字について」を追記.
  21. 2010/01/10(日)
  22. 2010/03/03(水)
  23. 2010/08/08(日)「終端文字について」に少し追記.
  24. 2011/01/19(水)「終端文字について」に少し追記.
  25. 2011/02/11(金)「終端文字について」を少し改定.
  26. 2012/10/11(木)「Shift_JIS と Windows-31J (CP932) の比較表」(1バイト文字の部分) を改定.
  27. 2012/12/09(日) 謎の検索ワード集 (シフトJIS編) を追加.
  28. 2013/03/10(日) EOF について追記.
  29. 2013/04/06(土)謎の検索ワード「UTF-8 ASCII 変換」を追加.
  30. 2013/07/31(水) 「謎の検索ワード集 (シフトJIS編)」に「Unicode(UTF-8,UTF-16) から Shift_JIS(SJIS,Windows-31J,CP932) へ変換できない文字」を追加.
  31. 2013/08/10(土)「サロゲート」に説明追記.
  32. 2013/08/13(火)「Shift_JIS と Windows-31J (CP932) の比較表」(1バイト文字の部分) を訂正.
  33. 2013/10/27(日)「Shift_JIS と Windows-31J (CP932) の比較表」に収録文字数を追記.
  34. 2015/02/09(月) EOF について少し追記.
  35. 2015/02/10(火) 「ASCII の NUL と DEL の本来の意味」に少し追記.
  36. 2015/03/01(日) 現行の UTF-8 (RFC3629) の表を改定,図を追記.
  37. 2015/07/31(金)「謎の検索ワード集 (シフトJIS編)」に,Unicode の補助文字に対応するシフトJISの文字について追記.
  38. 2016/04/09(土) EOF についてわずかに追記.


Copyright © 2007-2016 noocyte.
E-mail: relipmoced (a) yahoo.co.jp
  (" (a) " を半角のアットマークに書き替えてください.)
リンクはご自由に.
「noocyte のプログラミング研究室」トップページに戻る.


track feed noocyte