第5章:メモリアドレッシングと16進表記 - コンピュータアーキテクチャの基礎

導入:なぜ16進数なのか

コンピュータサイエンスにおいて16進数(hexadecimal)が広く使われる理由は、単なる慣習ではありません。2進数との数学的な関係性、人間の認知能力との相性、そしてハードウェア設計との親和性が、16進数を「コンピュータの言語」として確立させました。

ft_printfで%x%X%pを実装する際、私たちはコンピュータアーキテクチャの根幹に触れることになります。この章では、メモリアドレッシングの理論から始めて、なぜポインタを16進数で表示するのかを深く理解します。

---

第1部:von Neumannアーキテクチャとメモリ

1.1 ストアドプログラム方式の誕生

1945年、John von Neumannは「EDVACに関する報告書の第一草稿」で、現代コンピュータの基礎となるアーキテクチャを提案しました。その核心は「プログラムとデータを同じメモリに格納する」というアイデアでした。

von Neumannアーキテクチャの構成要素:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. 中央処理装置(CPU)
   - 制御装置(Control Unit)
   - 算術論理演算装置(ALU)
   - レジスタ群

2. 主記憶装置(Main Memory)
   - プログラム(命令)を格納
   - データを格納
   - アドレスによってアクセス

3. 入出力装置(I/O)

4. バス(Bus)
   - データバス
   - アドレスバス
   - 制御バス

メモリモデル:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

アドレス    内容
─────────────────
0x0000   │ 命令1     │
0x0004   │ 命令2     │
0x0008   │ 命令3     │
...      │ ...      │
0x1000   │ データ1   │
0x1004   │ データ2   │
...      │ ...      │

プログラムとデータが同じアドレス空間に存在
→ これがポインタ概念の基礎

1.2 メモリアドレスの概念

メモリは「番地付きの箱の並び」として抽象化できます。各箱(セル)には一意のアドレスが割り当てられています:

メモリの抽象モデル:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

物理的視点:
  連続した記憶素子の配列

論理的視点:
  アドレス → 内容 の写像関数
  M: Address → Value

バイトアドレッシング:
  現代のほとんどのアーキテクチャでは、
  最小のアドレス可能単位は1バイト(8ビット)

アドレス空間のサイズ:
  n ビットのアドレスバス → 2^n 個のアドレス

  8ビットCPU:   2^8  = 256 バイト = 256 B
  16ビットCPU:  2^16 = 65,536 バイト = 64 KB
  32ビットCPU:  2^32 = 4,294,967,296 バイト = 4 GB
  64ビットCPU:  2^64 = 18,446,744,073,709,551,616 バイト = 16 EB

1.3 仮想メモリとアドレス変換

現代のオペレーティングシステムは仮想メモリを使用し、各プロセスに独立したアドレス空間を提供します:

仮想メモリの概念:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

プロセスA        プロセスB         物理メモリ
┌──────────┐   ┌──────────┐      ┌──────────┐
│ 0x0000   │   │ 0x0000   │      │ 物理 0x0 │
│ ...      │   │ ...      │      │ ...      │
│ 0x1000 ──┼───┼──────────┼───→  │ 0x5000   │
│ ...      │   │ 0x1000 ──┼───→  │ 0x8000   │
└──────────┘   └──────────┘      └──────────┘

仮想アドレス ──[MMU]──→ 物理アドレス

利点:
- プロセス間のメモリ保護
- アドレス空間の独立性
- メモリの効率的な利用(ページング)
- 物理メモリ以上のアドレス空間

printfで表示されるアドレス:
- 通常は仮想アドレス
- 同じプログラムでも実行ごとに異なる場合がある
  (ASLR: Address Space Layout Randomization)

---

第2部:16進数表記の数学と歴史

2.1 16進数と2進数の関係

16進数が選ばれた理由は、2進数との完璧な対応関係にあります:

2進数と16進数の対応:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

16 = 2^4

つまり、16進数1桁 = 2進数4桁(1ニブル)

2進数    16進数
─────────────
0000     0
0001     1
0010     2
0011     3
0100     4
0101     5
0110     6
0111     7
1000     8
1001     9
1010     A (10)
1011     B (11)
1100     C (12)
1101     D (13)
1110     E (14)
1111     F (15)

例:32ビット値の変換
2進数:   1101 1110 1010 1101 1011 1110 1110 1111
16進数:    D    E    A    D    B    E    E    F
→ 0xDEADBEEF

この対応により:
- 2進数→16進数:4桁ずつグループ化
- 16進数→2進数:各桁を4ビットに展開
いずれも機械的に実行可能

2.2 なぜ8進数ではないのか

歴史的には8進数(octal)も使われていました:

8進数 vs 16進数:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8進数の利点(歴史的):
- 8 = 2^3、3ビットに対応
- 初期のコンピュータ(PDP-8など)との親和性
- UNIXのファイルパーミッション(chmod 755)

16進数の利点(現代的):
- 8ビット = 16進2桁(きれいに対応)
- 32ビット = 16進8桁
- 64ビット = 16進16桁

8進数だと:
- 8ビット = 2.67桁(きれいに割り切れない!)
- 32ビット = 10.67桁
- バイト境界と一致しない

バイト(8ビット)が標準単位となった現代では、
16進数がより自然な選択。

C言語での表記:
- 8進数: 0755(先頭に0)
- 16進数: 0xFF(先頭に0x)

2.3 16進数表記の歴史

16進数表記の発展:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1950年代以前:
- 様々な表記法が混在
- 一部のシステムでは0-9, u, v, w, x, y, z

1960年代:
- IBM System/360(1964年)が16進数を標準採用
- A-Fの表記が定着

表記の選択:
- なぜA-Fか?
  → 0-9の次のアルファベットを自然に使用
  → キーボードで入力しやすい
  → 印刷時に判読しやすい

大文字 vs 小文字:
- 歴史的に大文字が先(EBCDIC、初期ASCII)
- Unixの影響で小文字も普及
- C言語:どちらも有効(0xFFと0xff)
- printf:%Xで大文字、%xで小文字

---

第3部:バイト順序(エンディアン)

3.1 エンディアンの起源

「エンディアン」という用語は、ジョナサン・スウィフトの「ガリヴァー旅行記」に由来します。Danny Cohenが1980年の論文で、バイト順序の論争をこの小説の「卵の割り方論争」になぞらえました。

エンディアンの定義:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

32ビット値 0x12345678 をメモリに格納する場合:

リトルエンディアン(Little-Endian):
  下位バイトを低位アドレスに格納

  アドレス: 0x00  0x01  0x02  0x03
  内容:     0x78  0x56  0x34  0x12
            ↑最下位              ↑最上位

ビッグエンディアン(Big-Endian):
  上位バイトを低位アドレスに格納

  アドレス: 0x00  0x01  0x02  0x03
  内容:     0x12  0x34  0x56  0x78
            ↑最上位              ↑最下位

主要アーキテクチャ:
  リトルエンディアン: x86, x64, ARM(デフォルト)
  ビッグエンディアン: SPARC, PowerPC(古いMac), ネットワーク順序

3.2 エンディアンとポインタ表示

/* エンディアンの確認と影響 */

#include <stdio.h>
#include <stdint.h>

void check_endianness(void)
{
    uint32_t value = 0x12345678;
    uint8_t *bytes = (uint8_t *)&value;

    printf("Value: 0x%08X\n", value);
    printf("Memory layout:\n");
    printf("  Address+0: 0x%02X\n", bytes[0]);
    printf("  Address+1: 0x%02X\n", bytes[1]);
    printf("  Address+2: 0x%02X\n", bytes[2]);
    printf("  Address+3: 0x%02X\n", bytes[3]);

    if (bytes[0] == 0x78)
        printf("Little-Endian system\n");
    else
        printf("Big-Endian system\n");
}

/*
 * ポインタ表示への影響:
 *
 * ポインタ値(アドレス)自体は単一の整数値として扱われるため、
 * %p での表示にはエンディアンは影響しない。
 *
 * しかし、メモリダンプやデバッグでは
 * エンディアンを意識する必要がある。
 */

---

第4部:ポインタの本質

4.1 ポインタとは何か

C言語におけるポインタは、メモリアドレスを格納する変数です。しかし、その本質はより深いものです:

ポインタの多層的理解:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

レベル1:実装的理解
  ポインタ = メモリアドレスを格納する変数

レベル2:型理論的理解
  ポインタ型 T* = 「T型の値が格納されているメモリ位置への参照」

レベル3:抽象的理解
  ポインタ = 間接参照(indirection)の具現化
  「何かを指し示す」という概念のメモリ上での実現

C言語でのポインタ:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

int x = 42;
int *ptr = &x;

メモリ状態:
  アドレス    変数名    値
  0x1000     x         42
  0x2000     ptr       0x1000

ptr の値は 0x1000(xのアドレス)
*ptr は 42(ptrが指す先の値)

ポインタのサイズ:
  - 32ビットシステム: 4バイト
  - 64ビットシステム: 8バイト
  - ポインタが指す型のサイズとは無関係

4.2 ポインタ型とポインタ算術

/* ポインタ型の重要性 */

#include <stdio.h>

void pointer_arithmetic(void)
{
    int arr_int[] = {1, 2, 3, 4, 5};
    char arr_char[] = {'a', 'b', 'c', 'd', 'e'};

    int *pi = arr_int;
    char *pc = arr_char;

    printf("sizeof(int) = %zu\n", sizeof(int));   // 4
    printf("sizeof(char) = %zu\n", sizeof(char)); // 1

    printf("int pointer:\n");
    printf("  pi     = %p\n", (void *)pi);
    printf("  pi + 1 = %p\n", (void *)(pi + 1));
    printf("  差: %td バイト\n", (char *)(pi + 1) - (char *)pi);
    // 差は sizeof(int) = 4 バイト

    printf("char pointer:\n");
    printf("  pc     = %p\n", (void *)pc);
    printf("  pc + 1 = %p\n", (void *)(pc + 1));
    printf("  差: %td バイト\n", (char *)(pc + 1) - (char *)pc);
    // 差は sizeof(char) = 1 バイト
}

/*
 * ポインタ算術の規則:
 *
 * ptr + n = ptr + (n * sizeof(*ptr))
 *
 * これにより、配列の要素アクセスが
 * ポインタ算術で自然に表現できる:
 * arr[i] は *(arr + i) と等価
 */

4.3 NULLポインタの意味

NULLポインタの歴史と意味:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

歴史的背景:
- 1960年代、Tony Hoareが「null参照」を発明
- 後に彼自身が「10億ドルの間違い」と呼んだ

C言語でのNULL:
- <stddef.h> で定義
- 通常は ((void *)0) または 0
- 「何も指していない」ことを示す特別な値

アドレス0の意味:
- ほとんどのシステムで、アドレス0は
  ユーザープログラムからアクセス不可
- NULLポインタのデリファレンスは
  通常セグメンテーション違反

printfでのNULL表示:
- 実装依存
- "(nil)" または "0x0" または "(null)"
- ft_printfでは評価基準に合わせる

---

第5部:ft_printfにおける16進数出力

5.1 基数変換アルゴリズム

16進数への変換は、第4章で学んだ基数変換の応用です:

/* 16進数変換の実装 */

/*
 * 方法1:除算による変換
 *
 * N を16で繰り返し割り、余りを取得
 * 余りは最下位桁から順に得られる
 */
void convert_to_hex_division(unsigned int n)
{
    const char *digits = "0123456789abcdef";
    char buffer[9];  // 32ビット = 最大8桁 + 終端
    int i = 0;

    if (n == 0)
    {
        buffer[i++] = '0';
    }
    else
    {
        while (n > 0)
        {
            buffer[i++] = digits[n % 16];
            n /= 16;
        }
    }
    // bufferには逆順で格納されている
    // 出力時に反転が必要
}

/*
 * 方法2:ビット演算による変換
 *
 * 16 = 2^4 なので、4ビットずつ処理
 * 除算より効率的(多くのCPUで)
 */
void convert_to_hex_bitwise(unsigned int n)
{
    const char *digits = "0123456789abcdef";
    char buffer[9];
    int i = 0;

    if (n == 0)
    {
        buffer[i++] = '0';
    }
    else
    {
        while (n > 0)
        {
            buffer[i++] = digits[n & 0xF];  // 下位4ビット
            n >>= 4;                         // 4ビット右シフト
        }
    }
}

/*
 * なぜビット演算が効率的か:
 *
 * n % 16  → 除算命令(多くのCPUで遅い)
 * n & 0xF → AND命令(1サイクル)
 *
 * n / 16  → 除算命令
 * n >> 4  → シフト命令(1サイクル)
 *
 * ただし、現代のコンパイラは2の冪乗での除算を
 * 自動的にシフト演算に最適化することが多い
 */

5.2 %x と %X の実装

/* 完全な16進数出力実装 */

static int get_hex_len(unsigned int n)
{
    int len;

    if (n == 0)
        return 1;

    len = 0;
    while (n > 0)
    {
        len++;
        n >>= 4;
    }
    return len;
}

static int output_hex_digits(unsigned int n, int use_upper)
{
    const char *base;
    char buffer[9];
    int i;
    int len;

    base = use_upper ? "0123456789ABCDEF" : "0123456789abcdef";

    if (n == 0)
        return write(1, "0", 1);

    i = 0;
    while (n > 0)
    {
        buffer[i++] = base[n & 0xF];
        n >>= 4;
    }

    len = i;
    while (--i >= 0)
        write(1, &buffer[i], 1);

    return len;
}

int print_hex(unsigned int n, t_flags *flags, char specifier)
{
    int count;
    int hex_len;
    int prefix_len;
    int total_len;
    int padding;
    int use_upper;

    count = 0;
    use_upper = (specifier == 'X');

    /* 16進数の桁数を計算 */
    hex_len = get_hex_len(n);

    /* 精度0で値0の特殊ケース */
    if (flags->precision == 0 && n == 0)
        hex_len = 0;

    /* #フラグによるプレフィックス */
    prefix_len = 0;
    if ((flags->hash) && n != 0)
        prefix_len = 2;  /* "0x" or "0X" */

    /* 精度による最小桁数 */
    if (flags->precision > hex_len)
        hex_len = flags->precision;

    total_len = prefix_len + hex_len;

    /* パディング幅 */
    padding = 0;
    if (flags->width > total_len)
        padding = flags->width - total_len;

    /* 出力処理 */
    if (!flags->minus)
    {
        if (flags->zero && flags->precision < 0)
        {
            /* 0パディング:プレフィックス → 0 → 数値 */
            if (prefix_len)
                count += write(1, use_upper ? "0X" : "0x", 2);
            while (padding-- > 0)
                count += write(1, "0", 1);
        }
        else
        {
            /* スペースパディング:スペース → プレフィックス → 数値 */
            while (padding-- > 0)
                count += write(1, " ", 1);
            if (prefix_len)
                count += write(1, use_upper ? "0X" : "0x", 2);
        }
    }
    else
    {
        /* 左揃え */
        if (prefix_len)
            count += write(1, use_upper ? "0X" : "0x", 2);
    }

    /* 精度による0パディング */
    {
        int actual_len = get_hex_len(n);
        int zeros = flags->precision - actual_len;
        while (zeros-- > 0)
            count += write(1, "0", 1);
    }

    /* 数値本体 */
    if (hex_len > 0 && !(flags->precision == 0 && n == 0))
        count += output_hex_digits(n, use_upper);

    /* 左揃えの後パディング */
    if (flags->minus)
    {
        while (padding-- > 0)
            count += write(1, " ", 1);
    }

    return count;
}

5.3 #フラグの詳細

#フラグ(代替形式)の仕様:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

%#x : 0以外の値に "0x" プレフィックス
%#X : 0以外の値に "0X" プレフィックス

重要な仕様:
1. 値が0の場合、プレフィックスは付かない
   printf("%#x", 0);  → "0"("0x0"ではない)

2. プレフィックスは幅に含まれる
   printf("%#8x", 0x2a);  → "    0x2a"(8文字)

3. 0パディングとの相互作用
   printf("%#010x", 0x2a); → "0x0000002a"
   プレフィックスの後に0が続く

4. 精度との相互作用
   printf("%#.6x", 0x2a);  → "0x00002a"
   プレフィックスの後に精度分の0

---

第6部:ポインタ出力(%p)の実装

6.1 ポインタ型の安全な変換

/* ポインタから整数への変換 */

#include <stdint.h>

/*
 * uintptr_t について:
 *
 * - <stdint.h> で定義
 * - ポインタを格納できる符号なし整数型
 * - ポインタ→uintptr_t→ポインタ の変換が保証される
 *
 * なぜunsigned longではないのか:
 * - Windows 64ビット(LLP64)では
 *   sizeof(long) = 4、sizeof(void*) = 8
 * - uintptr_tは常にポインタと同サイズ
 */

int print_pointer(void *ptr)
{
    uintptr_t addr;
    const char *base = "0123456789abcdef";
    char buffer[17];  /* 64ビット: 最大16桁 + 終端 */
    int i;
    int count;

    count = 0;
    addr = (uintptr_t)ptr;

    /* "0x" プレフィックス(常に付く) */
    count += write(1, "0x", 2);

    /* アドレスが0の場合 */
    if (addr == 0)
    {
        count += write(1, "0", 1);
        return count;
    }

    /* 16進数に変換 */
    i = 0;
    while (addr > 0)
    {
        buffer[i++] = base[addr & 0xF];
        addr >>= 4;
    }

    /* 逆順で出力 */
    while (--i >= 0)
        count += write(1, &buffer[i], 1);

    return count;
}

6.2 NULLポインタの処理

/* NULLポインタの表示 */

/*
 * プラットフォーム間の違い:
 *
 * macOS (Apple clang):
 *   printf("%p", NULL);  → "0x0"
 *
 * Linux (glibc):
 *   printf("%p", NULL);  → "(nil)"
 *
 * 42の評価基準を確認し、適切な実装を選択
 */

/* macOS互換バージョン */
int print_pointer_macos(void *ptr)
{
    /* NULLも通常のアドレスとして扱う */
    return print_pointer(ptr);  /* "0x0" を出力 */
}

/* Linux glibc互換バージョン */
int print_pointer_linux(void *ptr, t_flags *flags)
{
    int count;
    int padding;
    const char *nil_str = "(nil)";

    count = 0;

    if (ptr == NULL)
    {
        /* "(nil)" を出力 */
        padding = flags->width - 5;

        if (!flags->minus)
        {
            while (padding-- > 0)
                count += write(1, " ", 1);
        }

        count += write(1, nil_str, 5);

        if (flags->minus)
        {
            while (padding-- > 0)
                count += write(1, " ", 1);
        }

        return count;
    }

    return print_pointer(ptr);
}

/* 幅指定対応の完全版 */
int print_pointer_full(void *ptr, t_flags *flags)
{
    uintptr_t addr;
    int addr_len;
    int total_len;
    int padding;
    int count;

    count = 0;
    addr = (uintptr_t)ptr;

    /* アドレスの桁数を計算 */
    if (addr == 0)
        addr_len = 1;
    else
    {
        addr_len = 0;
        uintptr_t tmp = addr;
        while (tmp > 0)
        {
            addr_len++;
            tmp >>= 4;
        }
    }

    /* "0x" + 桁数 */
    total_len = 2 + addr_len;

    /* パディング */
    padding = flags->width - total_len;
    if (padding < 0)
        padding = 0;

    /* 右揃え */
    if (!flags->minus)
    {
        while (padding-- > 0)
            count += write(1, " ", 1);
    }

    /* アドレス出力 */
    count += print_pointer(ptr);

    /* 左揃え */
    if (flags->minus)
    {
        while (padding-- > 0)
            count += write(1, " ", 1);
    }

    return count;
}

6.3 32ビットと64ビットの互換性

/* プラットフォーム非依存の実装 */

#include <stdint.h>
#include <limits.h>

/*
 * ポインタサイズの確認
 */
void check_pointer_size(void)
{
    printf("sizeof(void*) = %zu bytes\n", sizeof(void *));
    printf("sizeof(uintptr_t) = %zu bytes\n", sizeof(uintptr_t));

    #if UINTPTR_MAX == 0xFFFFFFFF
        printf("32-bit pointer\n");
        printf("Max hex digits: 8\n");
    #elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFF
        printf("64-bit pointer\n");
        printf("Max hex digits: 16\n");
    #endif
}

/*
 * バッファサイズの安全な定義
 */
#if UINTPTR_MAX == 0xFFFFFFFF
    #define PTR_HEX_DIGITS 8
#else
    #define PTR_HEX_DIGITS 16
#endif

int print_pointer_safe(void *ptr)
{
    char buffer[PTR_HEX_DIGITS + 1];
    /* ... 以下実装 ... */
}

---

第7部:パフォーマンスと最適化

7.1 システムコールのコスト

/* 出力効率の比較 */

/*
 * 非効率な実装:1文字ずつwrite
 */
int print_hex_slow(unsigned int n)
{
    const char *base = "0123456789abcdef";
    int count = 0;

    if (n >= 16)
        count = print_hex_slow(n >> 4);

    char c = base[n & 0xF];
    write(1, &c, 1);  /* 各桁でシステムコール */

    return count + 1;
}

/*
 * 効率的な実装:バッファリング
 */
int print_hex_fast(unsigned int n)
{
    const char *base = "0123456789abcdef";
    char buffer[9];
    int i;

    if (n == 0)
        return write(1, "0", 1);

    i = 8;
    buffer[i] = '\0';
    while (n > 0)
    {
        buffer[--i] = base[n & 0xF];
        n >>= 4;
    }

    return write(1, &buffer[i], 8 - i);  /* 1回のシステムコール */
}

/*
 * システムコールのオーバーヘッド:
 *
 * write() は:
 * 1. ユーザーモード → カーネルモード遷移
 * 2. カーネル内でのバッファ処理
 * 3. カーネルモード → ユーザーモード復帰
 *
 * これは数百〜数千CPUサイクルを消費
 *
 * 8桁の16進数出力:
 * - 1文字ずつ:8回のシステムコール
 * - バッファリング:1回のシステムコール
 * → 約8倍の効率向上
 */

7.2 ビット演算の最適化

/* 桁数計算の最適化 */

/* 方法1:ループ */
int hex_len_loop(unsigned int n)
{
    int len = 0;
    if (n == 0) return 1;
    while (n > 0)
    {
        len++;
        n >>= 4;
    }
    return len;
}

/* 方法2:二分探索(32ビット専用) */
int hex_len_binary(unsigned int n)
{
    if (n == 0) return 1;
    if (n < 0x10) return 1;
    if (n < 0x100) return 2;
    if (n < 0x1000) return 3;
    if (n < 0x10000) return 4;
    if (n < 0x100000) return 5;
    if (n < 0x1000000) return 6;
    if (n < 0x10000000) return 7;
    return 8;
}

/* 方法3:CLZ(Count Leading Zeros)命令を使用 */
#ifdef __GNUC__
int hex_len_clz(unsigned int n)
{
    if (n == 0) return 1;
    int leading_zeros = __builtin_clz(n);
    int bits = 32 - leading_zeros;
    return (bits + 3) / 4;  /* 4ビットで1桁 */
}
#endif

/*
 * パフォーマンス比較(100万回の呼び出し):
 *
 * ループ:     ~15ms
 * 二分探索:   ~5ms
 * CLZ:        ~3ms
 *
 * 実用上は二分探索で十分
 * CLZはGCC/Clang依存
 */

---

第8部:テストと検証

8.1 境界値テスト

/* 包括的なテストケース */

#include <stdio.h>
#include <limits.h>

void test_hex_boundaries(void)
{
    printf("=== Boundary Tests ===\n");

    /* 基本値 */
    printf("0:        %%x = %x\n", 0);
    printf("1:        %%x = %x\n", 1);
    printf("15:       %%x = %x\n", 15);  /* 1桁最大 */
    printf("16:       %%x = %x\n", 16);  /* 2桁最小 */

    /* バイト境界 */
    printf("255:      %%x = %x\n", 255);      /* 2桁最大 */
    printf("256:      %%x = %x\n", 256);      /* 3桁最小 */

    /* ワード境界 */
    printf("0xFFFF:   %%x = %x\n", 0xFFFF);
    printf("0x10000:  %%x = %x\n", 0x10000);

    /* 最大値 */
    printf("UINT_MAX: %%x = %x\n", UINT_MAX);
    printf("INT_MAX:  %%x = %x\n", INT_MAX);
    printf("INT_MIN:  %%x = %x\n", INT_MIN);
}

void test_hex_flags(void)
{
    unsigned int n = 0x2a;

    printf("\n=== Flag Tests ===\n");

    /* 大文字/小文字 */
    printf("%%x:    %x\n", n);
    printf("%%X:    %X\n", n);

    /* #フラグ */
    printf("%%#x:   %#x\n", n);
    printf("%%#X:   %#X\n", n);
    printf("%%#x 0: %#x\n", 0);  /* 0は特別 */

    /* 幅 */
    printf("%%10x:  |%10x|\n", n);
    printf("%%-10x: |%-10x|\n", n);
    printf("%%010x: |%010x|\n", n);

    /* 精度 */
    printf("%%.6x:  |%.6x|\n", n);
    printf("%%.0x:  |%.0x|\n", n);
    printf("%%.0x 0:|%.0x|\n", 0);

    /* 組み合わせ */
    printf("%%#10.6x: |%#10.6x|\n", n);
}

void test_pointer(void)
{
    int x = 42;
    void *ptr = &x;
    void *null = NULL;

    printf("\n=== Pointer Tests ===\n");

    printf("%%p:     %p\n", ptr);
    printf("NULL:   %p\n", null);
    printf("%%20p:  |%20p|\n", ptr);
    printf("%%-20p: |%-20p|\n", ptr);
}

8.2 printfとの比較テスト

/* ft_printfとprintfの出力比較 */

#include <string.h>

#define TEST(fmt, val) \
    do { \
        char buf_std[256], buf_ft[256]; \
        int len_std = sprintf(buf_std, fmt, val); \
        /* ft_printf版は実際の実装に合わせる */ \
        printf("Format: %-15s Value: %-12u\n", fmt, val); \
        printf("  printf:    |%s| (len=%d)\n", buf_std, len_std); \
    } while(0)

void run_comparison_tests(void)
{
    unsigned int values[] = {0, 1, 15, 16, 255, 256, 0xDEAD, 0xDEADBEEF};
    int n = sizeof(values) / sizeof(values[0]);

    for (int i = 0; i < n; i++)
    {
        TEST("%x", values[i]);
        TEST("%X", values[i]);
        TEST("%#x", values[i]);
        TEST("%10x", values[i]);
        TEST("%-10x", values[i]);
        TEST("%010x", values[i]);
        TEST("%.6x", values[i]);
        TEST("%#10.6x", values[i]);
        printf("\n");
    }
}

---

まとめ:メモリとアドレッシングの統一的理解

この章では、以下の知識を統合しました:

アーキテクチャの基礎:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- von Neumannアーキテクチャ
- メモリアドレッシングの概念
- 仮想メモリとアドレス空間
- エンディアンとバイト順序

16進数の理論:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- 2進数との数学的関係(16 = 2^4)
- 歴史的発展(IBM System/360)
- 8進数との比較

ポインタの本質:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- アドレスを格納する変数
- 型理論的な意味
- NULLポインタの扱い

実装技法:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- ビット演算による16進変換
- uintptr_tの使用
- プラットフォーム互換性
- パフォーマンス最適化

次章では、ft_printfの統合とテスト戦略について学びます。すべての変換指定子を組み合わせた複雑なフォーマット文字列の処理、エラーハンドリング、そして体系的なテスト手法を探求します。