はじめての C

C programming note*1
次は、小文字を 大文字に 変える プログラム。
最初の ASCII 依存プログラムでは、大文字と 小文字の コード番号が ちょうど 32 だけ 離れているのを 利用しています。 その部分の コードは、

while )((c = getc(fp))( != EOF) {
if ('a' <= c && c <= 'z')
fputc(c - 'a' + 'A', stdout); /* ASCII 依存 */
else
fputc(c, stdout);
}
と なり、それ以外のところは cat3 プログラム そのままです。
その応用で、逆に 大文字を 小文字に 変えるには、上の if文の 中身を、
if ('A' <= c && c <= 'Z')
fputc(c + 'a' - 'A', stdout);
に 変更すれば OK ですね。
これを、入力ファイルに 日本語が 混じっている場合を 想定して、改良していきます。

コンピュータの 記憶装置というのは 「小さなスイッチのようなものの集まり」 だと 述べた。 その 「オンか オフか」 だけを 表現できる 最小単位の スイッチ、これを ビット bit と呼ぶ。

このビットの 1つ1つを 扱っていたのでは 処理が 繁雑すぎるので、まとめて いくつかを 扱う必要が ある。 通常は 8つの ビットを 1組みにして 1バイト Byte という 単位で 扱う。 アドレス (番地) も、通常は このバイト単位で つけてある。

いままでのプログラムの 説明において 「1文字ずつ 読む」 のような 表現を してきた。 ここでいう 1文字とは、char型の 変数に 入る 1つ分のデータという 意味だった。 char型は 1バイトであると 考えてよい。(p94)

日本字の 扱いについて、

「文字」 の コードとして ASCII の 文字コードだけを 考える場合は、この 1バイトで 表現できる 範囲内に 収まるので、1文字 = 1バイトと 考えてもよかった。

ところが 日本語で 使われる文字は、とうてい 256種類では 足りない。 JIS では X0208 という規格で 「情報交換用符号」を 定めている。 この規格では 漢字 6355文字 および 漢字以外の 524文字 (ひらがな、カタカナなど)、合計 6879文字に コード (番号) を つけてある。 この文字コード体系では、たとえば 「愛」 の文字は 「16区 6点」 (16-6) と 表現する。 区は 1 〜 94、点も 1 〜 94 の 範囲なので、この方法でも 最大 8836文字しか 表現できないが、通常の 日本語文章の 表記に 用いるには これで 足りるとされている。

この区の番号 および 点の番号ならば、それぞれ 94通りなので、どちらも 1バイトで 表現し得る。 つまり 「区、点」の ペアで、2バイトを 使えば、この漢字コードが 表現できるわけだ。(p96)

その問題点として、

さて、実際には ASCII も この JIS X0208 も いっしょに (混在して) 使われる。 そのままの 文字コードの 値で、ただ 混在させたのでは うまく いかない。

たとえば 「圭」 の字は JIS X0208 では 23-29 なのだが、この区と点の値に それぞれ 32 だけ 「ゲタを はかせて」 大きい値として 使うので、つまり 55 と 61 の 2バイトになる。

しかし、これでは ASCII の 55 (数字の 7) と 61 (=記号) が 連続したものと 区別が できない。(p97)

では、実際に どう 処理するかというと、たとえば EUC_jp コードだと、

ASCII は (コード番号) 0 〜 127 の部分を 使うようにする。 JIS X0208 は 2バイトを それぞれ 128 だけ 大きい値へ シフトさせて (161 〜 254)、区別が つくようにする。(p97)

その処理は というと、

(この) 方法においては、「日本語文字は 2バイト、ASCII は 1バイト」 と、そのまま 単純に バイト数が 計算できるし、ふつうに fgetc() で 処理しても むずかしくはない。

fgetc() を 2回 呼び出せば、日本語文字 1文字分が 読めるわけだ。 日本語文字か ASCII かどうかを 判断するためには、とりあえず 1バイト 読んでみて、それが 日本語コードの 1バイト目か どうかを チェックする。(p97)

その部分の コードは 次のとおり、

while )((c = getc(fp))( != EOF)
if (iskanji(c)) { /* 日本語文字かを チェック */
fputc(c, stdout);
fputc(fgetc(fp), stdout); /* 2回 読み込む */
}
else if (islower(c)) /* 小文字の場合 */
fputc(toupper(c), stdout); /* 大文字に変換 */
else
fputc(c, stdout);
}
(islower() と toupper() については 後まわしに)
まず、最初の if文にある iskanji() 関数で、入力が 日本語文字か どうかを チェックしています。 この関数は マクロ (#define) で 定義しておきます。
#define iskanji(c) (0xa1 <= ((c) & 0xff) && ((c) & 0xff) <= 0xfe)

(このマクロについては) 解説は しないが、自分で 読み解いてみる つもりがない 人は、この時点では 「おまじない」 だと 思ってみてもよい。(p100)

つもりが ない ? フッ、挑発してますね ... (-_-)
では、わかる範囲で、
このマクロの iskanji() に続く ( ) の中は、この条件が 満たされれば、真を返す、ということですね。
この ( ) の式を 単純化すると、

(0xa1 <= (c') && (c') <= 0xfe)
と なります。0x で 始まるのは 16進数なので、これを 10進数に すると、
a -> 10, f -> 15, e ->14
0xa1 -> 1 + 10 x 16 = 161
0xfe -> 14 + 15 x 16 = 254
だから、上の式は、
(161 <= (c') && (c') <= 254)
と 同じで つまり、c が 日本語文字コード番号の 範囲 (161 〜 254) に あるかを チェックしている。
あと、*2 ...

*1:「作ってわかる Cプログラミング」

*2:c) & 0xff) は、ビット演算子 & を 使った マスク処理と 呼ばれるものです。 OS内部では ふつう システムが 扱いやすいように、メモリ配置を 8ビットずつ 区切って それを 逆転させています。 これを little endian と 呼びます。 このことは 通常、プログラムを 書くときには 意識しなくても かまいません。 ところが、日本語文字コードの場合には、big endian といって この左右が 逆に なっています。 それで、上のプログラムだと、初めの 1バイトを チェックするには、それが 日本語文字だとすると、2バイトのうちの 右側の 1バイトを 最初に もってきて 読む必要が あります。 ((c) & 0xff) と 書くことで、そのことが 実現できる、ということですね。 (シフト JIS の場合には、も少しだけ マクロが 複雑になります) まあ、初心者なので この程度の 理解ですけど((まちがってる ?