本物のC

C の前に:データの表現


現代のコンピュータは 0 と 1(電子回路のレベルでは電圧の高低)でデータを扱うので、様々なデータの表し方を考える必要があります。

整数の表現

基本は二進数です。

例えば 1 バイトにより表現できるのは 0000002 = 0 から 1111112 = 255 = 28 - 1 まで 256 通りの値です。(数字に付く 2 はそれが二進数である事を表します。)

しかし負の数を表すには少々工夫が必要で、よく使われるのは 2 の補数(two's complement)と呼ばれる表現です。

2 の補数では、例えば -6 を表す場合、まず 6 = 000001102 のビットを反転(0↔1)して、

00000110 → 11111001

ここに 1 を足します。

11111001 → 11111010

二進法なので 12 + 12 = 102 と繰り上がります。

2 の補数表現ではこの 111110102 が -6 を表すのですが、これが便利なのは元の 6 と足した時

00000110
+) 11111010
100000000

となる事です。100000000 の 1 は 1 バイト(二進 8 桁)からはみ出していますが、CPU はこれ切り捨てるので 000001102 + 111110102 が 0 となるのです。(切り捨てて何もしない訳ではなく、レジスタにある「キャリーフラグ」が立ちます。)

但しこれだとあらゆるビット列が正の数にも負の数にも解釈できるので、先頭ビットが 0 ならば正(または 0)、1 ならば負とします。これにより、以下の通りにビット列の意味がはっきりと決まります。

ビット列 表現される値
00000000 0
00000001 1
00000010 2
: :
01111111 127
10000000 -128
10000001 -127
10000010 -126
: :
11111111 -1

小数の表現

小数の表現には浮動小数点数(floating-point number)が使われます。

例えば 13.8 という数を考えましょう。13.8 = 1101.11001100…2 ですが、これを 2 = 102 で何回か割る(小数点を左にずらす)事で 1.… の形に持っていく事ができます。

13.8 = 1.10111001100…2 × 23

すると 13.8 という数は、(1.)10111001100… の部分と 3 の部分で表現する事ができます。これをそれぞれ仮数部(significand)及び指数部(exponent)と呼びます。

浮動小数点数にも色々あるのですが、標準規格 IEEE 754(国際規格としては ISO/IEC 60559)が定める binary16 という形式では、

  1. 符号に 1 ビット
  2. 指数部に 5 ビット
    • 指数部には 15 を足す
  3. 仮数部に 10 ビット(1. は含めない)

の 16 ビット= 2 バイトで表現します。

13.8 ならば

より、01001010 11100110 がその表現になります。

文字の表現

文字の表現方法は文字コード(character encoding)と呼ばれますが、それには

という二つの要素があります。

ASCII

現在使われている中で最も単純なのは ASCII(American Standard Code for Inomation Interchange)でしょう。これは制御文字、記号、英数字を 0x00 ~ 0x7F へ以下の様に割り当てます。

0 1 2 3 4 5 6 7 8 9 A B C D E F
0x0* NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
0x1* DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
0x2* ! " # $ % & ' ( ) * + , - . /
0x3* 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0x4* @ A B C D E F G H I J K L M N O
0x5* P Q R S T U V W X Y Z [ \ ] ^ _
0x6* ` a b c d e f g h i j k l m n o
0x7* p q r s t u v w x y z { | } ~ DEL

0x20 は半角スペースです。0x00 ~ 0x1F と 0x7F は制御文字(control character)と言って、改行などの特殊な意味を持ちます。(上表ではその意味を表した略号で代替しています。)

C 言語の規格に登場するものとしては以下の通りですが(「C の知識:リテラル」を参照)NUL、HT、LF、CR 以外を使う機会はそう無いでしょう。

コード 略号 名称
0x00 NUL ヌル文字
0x07 BEL ベル
0x08 BS バックスペース
0x09 HT 水平タブ
0x0A LF ラインフィード、改行
0x0B VT 垂直タブ
0x0C FF フォームフィード、改ページ
0x0D CR キャリッジリターン

Windows では、かつて主流だったテレタイプという出力機器(遠隔タイプライター的なもの)に倣って CR+LF の二文字で改行という事になっているので、UNIX 系で作成した LF 改行のファイルを開くと全く改行されずに表示されたりします。

Shift-JIS*

日本語の為の文字コードとしては Shift-JIS があります。元々は ASCII を拡張して(半角)カタカナを追加した JIS X 0201 と、日本語の表現に必要な文字を集めた JIS X 0208 があり、これらをやや無理やり融合する事で Shift-JIS が生まれました。

JIS X 0201 は ASCII が使わなかった先頭ビット 1 である 0x80 ~ 0xFF の内、 0xA1 ~ 0xDF へ以下の様にカタカナを割り当てています。

0 1 2 3 4 5 6 7 8 9 A B C D E F
0x8*
0x9*
0xA*
0xB* ソ
0xC*
0xD*
0xE*
0xF*

そして Shift-JIS は、JIS X 0201 も使わなかったコードの内 0x81 ~ 0x9F と 0xE0 ~ 0xEF を「次のビットの意味を変えるコード」として使う事で、JIS X 0208 の文字の表現を可能としました。言い換えると、Shift-JIS は 1 文字の表現に 1 バイトを使ったり 2 バイトを使ったりする可変長の文字コードです。

例えば 0xB1 は上の表を見ると「ア」が割り当てられていますが、その前に 0x81 ~ 0x9F または 0xE0 ~ 0xEF のコードがあると別の文字になります。0x82 0x81 では「こ」に、 0x88 0xB1 では「鯵」に、という具合です。

ビット列 文字集合
0x00 ~ 0x7F、0xA1 ~ 0xDF JIS X 0208
1 バイト目が 0x81 ~ 0x9F JIS X 0201 の 1 ~ 62 区
1 バイト目が 0xE0 ~ 0xEF JIS X 0201 の 63 ~ 94 区

2 バイト目として使われるのは 0x40 ~ 0x7E と 0x80 ~ 0xFE で、例えば 1 バイト目が 0x81 だと

0 1 2 3 4 5 6 7 8 9 A B C D E F
0x40 ´ ¨
0x50 _
0x60
0x70 ± ×
0x80 ÷ °
0x90 §
0xA0
0xB0
0xC0
0xD0
0xE0
0xF0

となっています。

Unicode*

Unicode は世界で使われるあらゆる文字を収録するという遠大な野望を持って始まり、今も拡張を続けています。

Unicode が各文字に振っている番号は U+0000 の様に表されます(十六進 4 桁の範囲の場合)。Unicode の最初の U+0000 ~ U+007F は ASCII と同じ文字へ同じ順に振られていおり、それ以降は Unicode 独自の並びです。例えば「あ」は U+3042 となっています。

そしてこの番号をバイト列で表す方法の一つが UTF-8 です。UTF-8 は RFC 3629 で標準化されており、 U+000000 ~ U+10FFFF の範囲の Unicode 文字を表現できます。

範囲 バイト列(二進法)
0000 0000 ~ 0000 007F 0xxxxxxx
0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx
0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~ 0010 FFFF 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx

二進数の番号が x に右詰めで入ります。例えば「あ」は 0x3042 = 110000010000102 なので、

11100011 10000001 10000010 → 0xE3 0x81 0x82

となります。