第2章:文字エンコーディングの世界
2.1 文字とコンピュータの出会い
2.1.1 電信からコンピュータへ:文字符号化の歴史
コンピュータが文字を扱う以前から、人類は遠距離通信のために文字を符号化する必要がありました。この歴史を理解することで、ASCIIコードの設計思想がより深く理解できます。
モールス信号(1838年)
サミュエル・モースが発明したモールス信号は、文字を「短点(・)」と「長点(−)」の組み合わせで表現する最初の実用的な電気通信符号でした。
頻出文字は短い符号、稀な文字は長い符号。これは「情報理論」の先駆けです。
| 文字 | モールス符号 | 頻度 | 備考 |
|:---:|:---:|:---:|:---|
| E | ・ | 12.7% | 最頻出の母音 → 最短 |
| T | − | 9.1% | 高頻度の子音 |
| A | ・− | 8.2% | |
| O | −−− | 7.5% | |
| I | ・・ | | |
| N | −・ | | |
| ... | ... | ... | ... |
| Q | −−・− | | 低頻度 → 長い |
| Z | −−・・ | | |
→ シャノンの情報理論(1948年)で数学的に正当化 「頻度の高い記号には短い符号を」= ハフマン符号化の原理
ボードー符号(1870年)
エミール・ボードーは、5ビットの固定長符号を開発しました。これにより、2^5 = 32種類の文字を表現できました。
| ビット列 | 文字 (Letters) | 文字 (Figures) |
|:---:|:---:|:---:|
| 00011 | A | - |
| 11001 | B | ? |
| 01110 | C | : |
| 01001 | D | $ |
| 00001 | E | 3 |
| ... | ... | ... |
特徴:
- 固定長5ビット(32通り)
- シフトによる切り替え(Letters/Figures)
問題点:
- シフト状態の追跡が必要
- 送信エラーで後続の文字が全て化ける
テレタイプと7ビット符号の登場
電信の発展に伴い、より多くの文字を扱う必要が生じました。1960年代、アメリカでASCII(American Standard Code for Information Interchange)が策定されます。
2.1.2 ASCIIの誕生と設計思想
ASCII策定の背景(1963年)
1960年代の状況:
コンピュータメーカーごとに独自の文字コード
IBM → EBCDIC (Extended Binary Coded Decimal Interchange Code)
DEC → 独自の6ビットコード
他多数 → 互換性なし
問題:
- データ交換が困難
- ソフトウェアの移植性なし
- 経済的損失
解決策:
1963年 ASA(現ANSI)がASCIIを策定
1968年 連邦政府が全コンピュータでASCII採用を義務化
ASCIIの設計原理
ASCIIは7ビットの文字コードで、128種類の文字を定義しています。その設計には深い考慮があります。
ASCIIは7ビットの文字コードで、128種類の文字を定義しています。
| 範囲 (Hex) | 範囲 (Dec) | 分類 | 内容 |
|:---|:---|:---|:---|
| 0x00-0x1F | 0-31 | 制御文字 | 33文字 (NUL, LF, CR, etc.) |
| 0x20 | 32 | スペース | 空白 |
| 0x21-0x2F | 33-47 | 記号 | ! " # $ % & ' ( ) + , - . / |
| 0x30-0x39 | 48-57 | 数字 | '0'-'9' |
| 0x3A-0x40 | 58-64 | 記号 | : ; < = > ? @ |
| 0x41-0x5A | 65-90 | 大文字 | 'A'-'Z' |
| 0x5B-0x60 | 91-96 | 記号 | [ \ ] ^ _ |
| 0x61-0x7A | 97-122 | 小文字 | 'a'-'z' |
| 0x7B-0x7E | 123-126 | 記号 | { | } ~ |
| 0x7F | 127 | DEL | 削除 |
視覚的グリッド(16進数):
| | 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_ | SP | ! | " | # | $ | % | & | ' | ( | ) | | + | , | - | . | / | | 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 |
ASCIIの設計における天才的配慮
1. 数字の連続配置 (0x30-0x39):
'0' = 0x30 = 0011 0000
'1' = 0x31 = 0011 0001
'2' = 0x32 = 0011 0010
...
'9' = 0x39 = 0011 1001
文字 → 数値変換:
digit = character - '0';
// '5' - '0' = 0x35 - 0x30 = 5
この設計により、ft_atoi の実装が単純になる
2. 大文字と小文字の関係:

大文字→小文字: c | 0x20 または c + 32
小文字→大文字: c & ~0x20 または c - 32
この設計により、ft_toupper, ft_tolower が効率的に実装可能
3. アルファベットの連続配置:
'A'(65) から 'Z'(90) まで連続
'a'(97) から 'z'(122) まで連続
アルファベット判定:
is_alpha = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
この設計により、ft_isalpha が単純な範囲チェックで実装可能
4. 制御文字の配置 (0x00-0x1F):
重要な制御文字:
0x00 NUL (Null) - 文字列終端
0x07 BEL (Bell) - 警告音
0x08 BS (Backspace) - 後退
0x09 HT (Horizontal Tab) - タブ
0x0A LF (Line Feed) - 改行
0x0D CR (Carriage Return) - 復帰
0x1B ESC (Escape) - エスケープシーケンス開始
制御文字判定:
is_control = (c >= 0 && c <= 31) || c == 127;
2.1.3 ASCIIの完全な一覧表
プログラマとして、ASCIIコードを暗記する必要はありませんが、主要な値は覚えておくと便利です。
ASCII コード表(完全版):
| Dec | Hex | Char | Description |
|:---:|:---:|:---:|:---|
| 0 | 0x00 | **NUL** | Null(文字列終端) |
| 1 | 0x01 | **SOH** | Start of Heading |
| 2 | 0x02 | **STX** | Start of Text |
| 3 | 0x03 | **ETX** | End of Text (Ctrl+C) |
| 4 | 0x04 | **EOT** | End of Transmission (Ctrl+D) |
| 5 | 0x05 | **ENQ** | Enquiry |
| 6 | 0x06 | **ACK** | Acknowledge |
| 7 | 0x07 | **BEL** | Bell (警告音) |
| 8 | 0x08 | **BS** | Backspace |
| 9 | 0x09 | **HT** | Horizontal Tab |
| 10 | 0x0A | **LF** | Line Feed (Unix改行) |
| 11 | 0x0B | **VT** | Vertical Tab |
| 12 | 0x0C | **FF** | Form Feed |
| 13 | 0x0D | **CR** | Carriage Return (Mac旧改行) |
| 14 | 0x0E | **SO** | Shift Out |
| 15 | 0x0F | **SI** | Shift In |
| 16 | 0x10 | **DLE** | Data Link Escape |
| 17 | 0x11 | **DC1** | Device Control 1 (XON) |
| 18 | 0x12 | **DC2** | Device Control 2 |
| 19 | 0x13 | **DC3** | Device Control 3 (XOFF) |
| 20 | 0x14 | **DC4** | Device Control 4 |
| 21 | 0x15 | **NAK** | Negative Acknowledge |
| 22 | 0x16 | **SYN** | Synchronous Idle |
| 23 | 0x17 | **ETB** | End of Transmission Block |
| 24 | 0x18 | **CAN** | Cancel |
| 25 | 0x19 | **EM** | End of Medium |
| 26 | 0x1A | **SUB** | Substitute (Ctrl+Z, Windows EOF) |
| 27 | 0x1B | **ESC** | Escape |
| 28 | 0x1C | **FS** | File Separator |
| 29 | 0x1D | **GS** | Group Separator |
| 30 | 0x1E | **RS** | Record Separator |
| 31 | 0x1F | **US** | Unit Separator |
| 32 | 0x20 | **(sp)** | Space(空白) |
| 33 | 0x21 | **!** | Exclamation mark |
| 34 | 0x22 | **"** | Double quote |
| 35 | 0x23 | **#** | Number sign / Hash |
| 36 | 0x24 | **$** | Dollar sign |
| 37 | 0x25 | **%** | Percent |
| 38 | 0x26 | **&** | Ampersand |
| 39 | 0x27 | **'** | Single quote |
| 40 | 0x28 | **(** | Left parenthesis |
| 41 | 0x29 | **)** | Right parenthesis |
| 42 | 0x2A | ***** | Asterisk |
| 43 | 0x2B | **+** | Plus |
| 44 | 0x2C | **,** | Comma |
| 45 | 0x2D | **-** | Hyphen / Minus |
| 46 | 0x2E | **.** | Period / Dot |
| 47 | 0x2F | **/** | Slash |
| 48 | 0x30 | **0** | Digit zero |
| 49 | 0x31 | **1** | Digit one |
| ... | ... | ... | ... |
| 57 | 0x39 | **9** | Digit nine |
| 58 | 0x3A | **:** | Colon |
| 59 | 0x3B | **;** | Semicolon |
| 60 | 0x3C | **<** | Less than |
| 61 | 0x3D | **=** | Equals |
| 62 | 0x3E | **>** | Greater than |
| 63 | 0x3F | **?** | Question mark |
| 64 | 0x40 | **@** | At sign |
| 65 | 0x41 | **A** | Uppercase A |
| ... | ... | ... | ... |
| 90 | 0x5A | **Z** | Uppercase Z |
| 91 | 0x5B | **[** | Left square bracket |
| 92 | 0x5C | **\** | Backslash |
| 93 | 0x5D | **]** | Right square bracket |
| 94 | 0x5E | **^** | Caret |
| 95 | 0x5F | **_** | Underscore |
| 96 | 0x60 | **`** | Grave accent / Backtick |
| 97 | 0x61 | **a** | Lowercase a |
| ... | ... | ... | ... |
| 122 | 0x7A | **z** | Lowercase z |
| 123 | 0x7B | **{** | Left curly brace |
| 124 | 0x7C | **\|** | Vertical bar / Pipe |
| 125 | 0x7D | **}** | Right curly brace |
| 126 | 0x7E | **~** | Tilde |
| 127 | 0x7F | **DEL** | Delete |
覚えておくと便利な値:
'0' = 48 = 0x30 'A' = 65 = 0x41 'a' = 97 = 0x61
'\n' = 10 = 0x0A ' ' = 32 = 0x20 '\0' = 0 = 0x00
2.2 ASCIIを超えて:文字エンコーディングの進化
2.2.1 8ビット拡張と混乱の時代
ASCIIは7ビットで設計されましたが、コンピュータは8ビット(1バイト)を基本単位としています。残りの1ビットをどう使うかで、様々な拡張が生まれました。
8ビット拡張の乱立:

代表的な拡張:
1. ISO 8859-1 (Latin-1) - 西欧
0x80-0xFF: àáâ ñ ö ü など西欧文字
2. ISO 8859-5 - キリル文字
0x80-0xFF: А Б В Г Д など
3. ISO 8859-15 (Latin-9)
Latin-1 + ユーロ記号(€)
4. Windows-1252 (CP1252)
Latin-1の制御文字領域に追加文字
5. Shift_JIS (日本)
1バイト: ASCII + 半角カナ
2バイト: 漢字、全角文字
6. EUC-JP (日本)
Unix系で使用された日本語エンコーディング
7. Big5 (繁体字中国語)
8. GB2312 / GBK (簡体字中国語)
9. EUC-KR (韓国語)
文字化けの発生メカニズム
文字化け(mojibake)の例:
送信側 (Shift_JIS):
"日本語" = 93 FA 96 7B 8C EA
受信側が ISO 8859-1 と解釈:
93 → "ô" (Latin-1)
FA → "ú" (Latin-1)
96 → "û" (Latin-1)
7B → "{" (ASCII)
8C → "Œ" (Latin-1)
EA → "ê" (Latin-1)
結果: "日本語" → "ôúû{Œê" ???
問題の本質:
- 同じバイト列でも、エンコーディングによって解釈が異なる
- 送信者と受信者のエンコーディングが一致しないと文字化け
2.2.2 Unicodeの登場:統一への道
Unicode の理念(1991年)
Unicode の目標:
「世界中の全ての文字に、唯一の番号(コードポイント)を割り当てる」
日本語 "あ" = U+3042
中国語 "中" = U+4E2D
アラビア語 "ا" = U+0627
絵文字 "😀" = U+1F600
Unicode の構成:
コードポイント範囲: U+0000 〜 U+10FFFF
理論上の文字数: 1,114,112 文字
現在の定義済み: 約150,000文字(Unicode 15.0)
平面(Plane)の構成:
Plane 0 (BMP): U+0000 - U+FFFF 基本多言語面(ほとんどの文字)
Plane 1 (SMP): U+10000 - U+1FFFF 追加多言語面(古代文字、絵文字)
Plane 2 (SIP): U+20000 - U+2FFFF 追加漢字面
...
Plane 16: U+100000 - U+10FFFF
UTF-8:可変長エンコーディングの傑作
UTF-8は、ケン・トンプソン(Unixの共同開発者)とロブ・パイクが1992年に設計した、可変長のUnicodeエンコーディングです。
UTF-8 のエンコーディング規則:
| コードポイント範囲 | UTF-8 バイト列 | 備考 |
|:---|:---|:---|
| `U+0000 - U+007F` | `0xxxxxxx` | 1バイト、ASCIIと完全互換 |
| `U+0080 - U+07FF` | `110xxxxx 10xxxxxx` | 2バイト (ラテン拡張等) |
| `U+0800 - U+FFFF` | `1110xxxx 10xxxxxx 10xxxxxx` | 3バイト (日本語、中国語等) |
| `U+10000 - U+10FFFF` | `11110xxx 10xxxxxx 10xxxxxx 10xxxxxx` | 4バイト (絵文字、古代文字) |
例: "A" (U+0041)
0041 = 0100 0001
→ 0x41 (1バイト、ASCIIそのまま)
例: "é" (U+00E9)
00E9 = 1110 1001
→ 110 00011 10 101001
→ 0xC3 0xA9 (2バイト)
例: "あ" (U+3042)
3042 = 0011 0000 0100 0010
→ 1110 0011 10 000001 10 000010
→ 0xE3 0x81 0x82 (3バイト)
例: "😀" (U+1F600)
1F600 = 0001 1111 0110 0000 0000
→ 11110 000 10 011111 10 011000 10 000000
→ 0xF0 0x9F 0x98 0x80 (4バイト)
UTF-8の優れた設計
UTF-8 の利点:
1. ASCII互換性
ASCII文字はそのまま1バイト
既存のASCIIデータはそのままUTF-8として有効
C言語の文字列処理関数の多くがそのまま動作
2. 自己同期性
どのバイトからでも、文字の境界を検出可能
・1バイト目: 0xxxxxxx, 110xxxxx, 1110xxxx, 11110xxx
・継続バイト: 10xxxxxx
途中から読み始めても復帰可能:
... 10xxxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx ...
↑
ここが文字の開始
3. バイト順序の問題なし
UTF-16/32 と異なり、エンディアンに依存しない
4. NUL安全
U+0000以外の文字に0x00バイトが現れない
C言語の文字列(NUL終端)と互換
5. ソート順序の保持
バイト列としてソートすると、コードポイント順になる
2.3 ビット演算の数学的基礎
2.3.1 ブール代数と論理演算
文字処理関数を効率的に実装するためには、ビット演算の理解が不可欠です。ビット演算は、19世紀の数学者ジョージ・ブールが確立した「ブール代数」に基づいています。
ブール代数の基本演算:
1. AND(論理積、∧、&)
両方が1のときのみ1
**真理値表**:
| A | B | A & B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
ビット列での例:
1010 1100
& 1100 1010
──────────
1000 1000
用途: 特定ビットの抽出(マスク)
x & 0x0F → 下位4ビットを抽出
2. OR(論理和、∨、|)
どちらかが1なら1
**真理値表**:
| A | B | A \| B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
ビット列での例:
1010 1100
| 1100 1010
──────────
1110 1110
用途: 特定ビットのセット
x | 0x20 → 第5ビットを1にセット
3. XOR(排他的論理和、⊕、^)
異なるときのみ1
**真理値表**:
| A | B | A ^ B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
ビット列での例:
1010 1100
^ 1100 1010
──────────
0110 0110
重要な性質:
A ^ A = 0 (自分自身とのXORは0)
A ^ 0 = A (0とのXORは変化なし)
A ^ B ^ B = A (2回XORすると元に戻る)
用途: ビットの反転、暗号化、スワップ
// 一時変数なしでスワップ
a ^= b; b ^= a; a ^= b;
4. NOT(論理否定、¬、~)
ビットの反転
~0 = 1, ~1 = 0
ビット列での例:
~ 1010 1100
──────────
0101 0011
用途: ビットのクリア
x & ~0x20 → 第5ビットを0にクリア
2.3.2 シフト演算
シフト演算:
1. 左シフト (<<)
ビットを左に移動、空いた右側は0で埋まる
00001010 << 2 = 00101000
(10 << 2 = 40)
数学的意味: × 2^n
x << n は x × 2^n と等価(オーバーフローしない場合)
2. 右シフト (>>)
a) 論理右シフト(符号なし整数)
空いた左側は0で埋まる
10100000 >> 2 = 00101000
b) 算術右シフト(符号付き整数、多くの実装)
空いた左側は符号ビットで埋まる
10100000 >> 2 = 11101000 (負の数の場合)
数学的意味: ÷ 2^n(切り捨て)
x >> n は x ÷ 2^n と等価
シフト演算の応用:
1. 2のべき乗の乗除算(高速)
x * 8 → x << 3 (8 = 2^3)
x / 4 → x >> 2 (4 = 2^2)
2. 特定ビットへのアクセス
// 第nビットを1にする
mask = 1 << n;
// 第nビットを取得
bit = (x >> n) & 1;
3. ビットフィールドの構築
// RGB値を32ビット整数に格納
color = (r << 16) | (g << 8) | b;
2.3.3 ビット演算のイディオム
文字処理で頻繁に使用されるビット演算のパターンを紹介します。
// 1. 特定ビットの操作
// 第nビットをセット
#define SET_BIT(x, n) ((x) | (1 << (n)))
// 第nビットをクリア
#define CLEAR_BIT(x, n) ((x) & ~(1 << (n)))
// 第nビットを反転
#define TOGGLE_BIT(x, n) ((x) ^ (1 << (n)))
// 第nビットを取得
#define GET_BIT(x, n) (((x) >> (n)) & 1)
// 2. 大文字・小文字変換(ASCIIの性質を利用)
// 小文字→大文字: 第5ビットをクリア
#define TO_UPPER(c) ((c) & ~0x20)
// 'a' (0x61) & ~0x20 = 'A' (0x41)
// 大文字→小文字: 第5ビットをセット
#define TO_LOWER(c) ((c) | 0x20)
// 'A' (0x41) | 0x20 = 'a' (0x61)
// 大文字・小文字を反転
#define FLIP_CASE(c) ((c) ^ 0x20)
// 3. 数字文字と数値の変換
// '0'-'9' → 0-9
#define CHAR_TO_DIGIT(c) ((c) - '0')
// '5' - '0' = 0x35 - 0x30 = 5
// 0-9 → '0'-'9'
#define DIGIT_TO_CHAR(n) ((n) + '0')
// 5 + '0' = 5 + 0x30 = 0x35 = '5'
// 4. 2のべき乗判定
#define IS_POWER_OF_2(x) ((x) && !((x) & ((x) - 1)))
// x = 8 (1000), x-1 = 7 (0111)
// 1000 & 0111 = 0000 → true
// x = 6 (0110), x-1 = 5 (0101)
// 0110 & 0101 = 0100 → false
// 5. 絶対値(分岐なし、整数のみ)
// 注意: INT_MIN では未定義動作
int abs_value(int x) {
int mask = x >> 31; // 負なら-1(全ビット1)、正なら0
return (x + mask) ^ mask;
}
2.4 Libft文字処理関数の実装
ここまでの基礎知識を踏まえ、Libftの文字処理関数を実装します。
2.4.1 ft_isalpha - アルファベット判定
/*
* ft_isalpha - 文字がアルファベットかどうかを判定
*
* 仕様:
* 引数cがアルファベット(A-Z, a-z)なら0以外を返す
* それ以外は0を返す
*
* 引数がintである理由:
* EOF(-1)を含む全ての可能な入力を受け入れるため
* (charは通常-128〜127または0〜255の範囲)
*/
// 実装1: 標準的な範囲チェック
int ft_isalpha(int c)
{
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
// 実装2: ビット演算を使った最適化
int ft_isalpha_optimized(int c)
{
// 大文字・小文字を統一してからチェック
// c | 0x20 で小文字に統一('A'→'a', 'a'→'a')
unsigned int lower = c | 0x20;
return (lower >= 'a' && lower <= 'z');
}
// 実装3: ルックアップテーブル(最高速だがメモリ使用)
static const unsigned char alpha_table[256] = {
// 0-64: 0
[0 ... 64] = 0,
// 65-90 (A-Z): 1
['A' ... 'Z'] = 1,
// 91-96: 0
[91 ... 96] = 0,
// 97-122 (a-z): 1
['a' ... 'z'] = 1,
// 123-255: 0
[123 ... 255] = 0
};
int ft_isalpha_table(int c)
{
if (c < 0 || c > 255)
return 0;
return alpha_table[(unsigned char)c];
}
エッジケースと注意点
// テストケース
assert(ft_isalpha('A') != 0); // 大文字
assert(ft_isalpha('Z') != 0); // 大文字境界
assert(ft_isalpha('a') != 0); // 小文字
assert(ft_isalpha('z') != 0); // 小文字境界
assert(ft_isalpha('0') == 0); // 数字
assert(ft_isalpha(' ') == 0); // 空白
assert(ft_isalpha('\0') == 0); // NUL
assert(ft_isalpha('@') == 0); // 'A'の直前
assert(ft_isalpha('[') == 0); // 'Z'の直後
assert(ft_isalpha('`') == 0); // 'a'の直前
assert(ft_isalpha('{') == 0); // 'z'の直後
assert(ft_isalpha(-1) == 0); // EOF
assert(ft_isalpha(128) == 0); // 非ASCII
2.4.2 ft_isdigit - 数字判定
/*
* ft_isdigit - 文字が数字かどうかを判定
*
* ASCIIにおける数字:
* '0' = 48 = 0x30
* '1' = 49 = 0x31
* ...
* '9' = 57 = 0x39
*
* 連続した配置により、単純な範囲チェックで実装可能
*/
int ft_isdigit(int c)
{
return (c >= '0' && c <= '9');
}
// 数値への変換と組み合わせた使用例
int ft_atoi_simple(const char *str)
{
int result = 0;
while (ft_isdigit(*str))
{
result = result * 10 + (*str - '0');
str++;
}
return result;
}
2.4.3 ft_isalnum - 英数字判定
/*
* ft_isalnum - 文字がアルファベットまたは数字かどうかを判定
*
* 実装方針:
* 既に実装したft_isalphaとft_isdigitを組み合わせる
* → コードの再利用、保守性の向上
*/
int ft_isalnum(int c)
{
return (ft_isalpha(c) || ft_isdigit(c));
}
// または、独立した実装
int ft_isalnum_standalone(int c)
{
return ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9'));
}
2.4.4 ft_isascii - ASCII判定
/*
* ft_isascii - 文字がASCII文字かどうかを判定
*
* ASCII範囲: 0-127 (7ビット)
* 0x00-0x7F
*
* 注意: 制御文字(0-31, 127)も含む
*/
int ft_isascii(int c)
{
return (c >= 0 && c <= 127);
}
// ビット演算版(符号なし前提)
int ft_isascii_bit(int c)
{
// 上位ビットが0かどうか
return !(c & ~0x7F);
}
2.4.5 ft_isprint - 印字可能文字判定
/*
* ft_isprint - 文字が印字可能かどうかを判定
*
* 印字可能文字: 32-126 (0x20-0x7E)
* スペース(32)から~(126)まで
*
* 含まれないもの:
* 制御文字 (0-31)
* DEL (127)
* 非ASCII (128-)
*/
int ft_isprint(int c)
{
return (c >= 32 && c <= 126);
}
// 空白文字(isspace)との違いを理解する
// isprint: スペース(32)は印字可能として含む
// isspace: タブ、改行なども含む
//
// isspace が true となる文字:
// ' ' (32), '\t' (9), '\n' (10), '\v' (11), '\f' (12), '\r' (13)
2.4.6 ft_toupper - 大文字変換
/*
* ft_toupper - 小文字を大文字に変換
*
* ASCII設計の恩恵:
* 'a' = 0x61 = 0110 0001
* 'A' = 0x41 = 0100 0001
* 差 = 0x20 = 0010 0000 (32)
*
* 小文字 - 32 = 大文字
* または
* 小文字 & ~0x20 = 大文字(第5ビットをクリア)
*/
// 実装1: 条件分岐
int ft_toupper(int c)
{
if (c >= 'a' && c <= 'z')
return (c - 32);
return (c);
}
// 実装2: ビット演算
int ft_toupper_bit(int c)
{
if (c >= 'a' && c <= 'z')
return (c & ~0x20);
return (c);
}
// 実装3: 条件演算子
int ft_toupper_ternary(int c)
{
return ((c >= 'a' && c <= 'z') ? c - 32 : c);
}
2.4.7 ft_tolower - 小文字変換
/*
* ft_tolower - 大文字を小文字に変換
*
* 変換方法:
* 大文字 + 32 = 小文字
* または
* 大文字 | 0x20 = 小文字(第5ビットをセット)
*/
int ft_tolower(int c)
{
if (c >= 'A' && c <= 'Z')
return (c + 32);
return (c);
}
// ビット演算版
int ft_tolower_bit(int c)
{
if (c >= 'A' && c <= 'Z')
return (c | 0x20);
return (c);
}
2.5 標準ライブラリとの比較
2.5.1 ctype.hの関数との違い
#include <ctype.h>
// 標準ライブラリのisalpha等は、ロケールに依存する可能性がある
// 例: ドイツ語ロケールでは、'ä', 'ö', 'ü' もアルファベット扱いかもしれない
// Libftの関数は、純粋なASCII範囲のみを対象とする
// 戻り値の違い:
// 標準: 0以外の値(具体的な値は未定義)
// Libft: 1または0を返す実装が多い(仕様上は0以外で良い)
// 使用例での注意
void example(void)
{
int c = getchar();
// 良い使い方
if (isalpha(c)) { // 0以外かどうかをチェック
// ...
}
// 悪い使い方
if (isalpha(c) == 1) { // 標準ではこれはダメ
// isalphaが2や4を返すかもしれない
}
}
2.5.2 パフォーマンスの比較
// 各実装のパフォーマンス比較
// 1. 単純な範囲チェック(最も一般的)
// - 分岐: 2-4回の比較
// - 現代のCPUでは十分高速
// 2. ビット演算(理論上は高速)
// - 分岐: 1-2回の比較
// - 実際は分岐予測により差が出にくい
// 3. ルックアップテーブル(大量処理向け)
// - 分岐: なし(テーブル参照のみ)
// - キャッシュに載っていれば最速
// - メモリを256バイト消費
// ベンチマーク例(100万回の呼び出し)
// 環境: Intel Core i7, GCC -O2
// ft_isalpha(範囲チェック): 約 2ms
// ft_isalpha_bit(ビット演算): 約 2ms
// ft_isalpha_table(テーブル): 約 1.5ms
// 標準 isalpha: 約 2ms
// 結論: 大量処理でなければ、可読性を優先すべき
2.6 文字処理の実践的応用
2.6.1 大文字小文字を無視した文字列比較
/*
* ft_strcasecmp - 大文字小文字を無視した文字列比較
*
* libftには含まれないが、実務でよく使う関数
*/
int ft_strcasecmp(const char *s1, const char *s2)
{
while (*s1 && *s2)
{
int diff = ft_tolower((unsigned char)*s1) -
ft_tolower((unsigned char)*s2);
if (diff != 0)
return diff;
s1++;
s2++;
}
return ft_tolower((unsigned char)*s1) -
ft_tolower((unsigned char)*s2);
}
// 使用例
// ft_strcasecmp("Hello", "HELLO") == 0
// ft_strcasecmp("abc", "abd") < 0
2.6.2 文字列の正規化
/*
* 文字列を小文字に正規化(検索、比較の前処理)
*/
void ft_str_to_lower(char *str)
{
while (*str)
{
*str = ft_tolower((unsigned char)*str);
str++;
}
}
/*
* 識別子の正規化(C言語の変数名など)
* アルファベット、数字、アンダースコア以外を除去
*/
void ft_normalize_identifier(char *dst, const char *src)
{
while (*src)
{
if (ft_isalnum((unsigned char)*src) || *src == '_')
*dst++ = *src;
src++;
}
*dst = '\0';
}
2.6.3 簡易トークナイザ
/*
* 文字種に基づくトークン分類
*/
typedef enum e_token_type
{
TOKEN_WORD, // アルファベット列
TOKEN_NUMBER, // 数字列
TOKEN_SYMBOL, // 記号
TOKEN_SPACE, // 空白
TOKEN_END // 終端
} t_token_type;
t_token_type get_token_type(int c)
{
if (c == '\0')
return TOKEN_END;
if (ft_isalpha(c))
return TOKEN_WORD;
if (ft_isdigit(c))
return TOKEN_NUMBER;
if (c == ' ' || c == '\t' || c == '\n')
return TOKEN_SPACE;
return TOKEN_SYMBOL;
}
// 使用例: 入力を分類しながら処理
void process_input(const char *input)
{
while (*input)
{
t_token_type type = get_token_type(*input);
switch (type)
{
case TOKEN_WORD:
// 単語の処理
break;
case TOKEN_NUMBER:
// 数値の処理
break;
// ...
}
input++;
}
}
2.7 よくある間違いとデバッグ
2.7.1 符号付き文字の問題
// 問題のあるコード
int ft_isalpha_buggy(char c) // charは符号付きの可能性
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
// 問題: charが符号付きの場合、128-255の値は負の数として扱われる
// 例: 0x80 = 128 (unsigned) = -128 (signed)
// 解決策: unsigned charにキャストするか、intで受け取る
int ft_isalpha_fixed(int c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
// または呼び出し側でキャスト
// ft_isalpha((unsigned char)str[i])
2.7.2 ロケールの影響
// Libftの文字関数はロケールに依存しない
// 標準のctype.h関数は依存する可能性がある
#include <locale.h>
#include <ctype.h>
void locale_example(void)
{
// デフォルトロケール("C"ロケール)
setlocale(LC_ALL, "C");
printf("isalpha('ä') = %d\n", isalpha((unsigned char)'ä')); // 0
// ドイツ語ロケール
setlocale(LC_ALL, "de_DE.UTF-8");
printf("isalpha('ä') = %d\n", isalpha((unsigned char)'ä')); // 1かも
// Libftは常に同じ結果
printf("ft_isalpha('ä') = %d\n", ft_isalpha((unsigned char)'ä')); // 0
}
2.8 まとめと次章への展望
本章では、以下のことを学びました:
- 文字符号化の歴史: モールス信号からASCII、そしてUnicodeへの進化
- ASCIIの設計思想: なぜ文字がこのように配置されているか
- UTF-8の仕組み: 可変長エンコーディングの原理と利点
- ビット演算の数学: ブール代数、シフト演算、実用的なイディオム
- Libft文字関数の実装: isalpha, isdigit, toupper, tolowerなど
次の第3章では、メモリの深淵を探検します。コンピュータのメモリ階層(レジスタ、キャッシュ、RAM、ストレージ)、仮想メモリの仕組み、そしてポインタの本質を深く理解した上で、memset、memcpy、memmoveなどのメモリ操作関数の実装に進みます。
メモリを理解することは、C言語プログラミングの核心です。ポインタが「なぜそう動くのか」を根本から理解することで、バグの少ない、効率的なコードが書けるようになります。