C言語データ型 チートシート

cheatsheet

数値を扱う基本的な型

整数を格納するためのデータ型です。サイズや符号の有無によって複数の種類があります。

基本整数型

処理系(コンパイラや実行環境)によってサイズが異なる可能性がある基本的な整数型です。

型名 説明 一般的なサイズ範囲 (最小保証) 値の範囲 (最小保証)
char 最小の整数型。文字コードの格納にも使われる。符号の有無は処理系定義。 8ビット以上 (CHAR_BIT マクロで確認可能) signed char: -127 〜 +127
unsigned char: 0 〜 255
short
(short int)
短整数型。 16ビット以上 -32767 〜 +32767
int 基本的な整数型。CPUにとって最も効率的なサイズであることが多い。 16ビット以上 (short以上) -32767 〜 +32767
long
(long int)
長整数型。 32ビット以上 (int以上) -2147483647 〜 +2147483647
long long
(long long int)
より大きな長整数型 (C99以降)。 64ビット以上 (long以上) -9223372036854775807 〜 +9223372036854775807

⚠️ 注意: 上記のサイズと範囲はC規格で保証される最小値です。実際の環境ではこれより大きいことが一般的です。<limits.h> ヘッダで定義されるマクロ (例: INT_MAX, ULONG_MAX) を使うことで、実行環境での正確な範囲を確認できます。

符号付き・符号無し

signed (符号付き、デフォルト) または unsigned (符号無し) を型名の前につけることで、負数を扱うか、0以上の値のみを扱うかを指定できます。

  • signed char: 符号付き文字型 (-128〜127の範囲が一般的)
  • unsigned char: 符号無し文字型 (0〜255の範囲が一般的)
  • unsigned short: 符号無し短整数型
  • unsigned int: 符号無し整数型
  • unsigned long: 符号無し長整数型
  • unsigned long long: 符号無し超長整数型 (C99以降)

char の符号は処理系定義ですが、明示的に signed または unsigned をつけることで挙動を確定できます。

サイズ固定整数型 (<stdint.h>, C99以降)

環境に依存せず、ビット幅が保証された整数型を使用したい場合に利用します。移植性の高いコードを書く際に便利です。✨

型名 説明
int8_t, int16_t, int32_t, int64_t 正確に指定ビット幅を持つ符号付き整数型
uint8_t, uint16_t, uint32_t, uint64_t 正確に指定ビット幅を持つ符号無し整数型
int_least8_t, int_least16_t, int_least32_t, int_least64_t 少なくとも指定ビット幅を持つ最小の符号付き整数型
uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t 少なくとも指定ビット幅を持つ最小の符号無し整数型
int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t 少なくとも指定ビット幅を持つ最速の符号付き整数型
uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t 少なくとも指定ビット幅を持つ最速の符号無し整数型
intptr_t ポインタを格納できる大きさを持つ符号付き整数型 (任意)
uintptr_t ポインタを格納できる大きさを持つ符号無し整数型 (任意)
intmax_t 最大の符号付き整数型
uintmax_t 最大の符号無し整数型

これらの型を使用するには、<stdint.h> ヘッダをインクルードする必要があります。

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

int main(void) {
    int32_t exact_32bit_signed = -12345;
    uint64_t exact_64bit_unsigned = 9876543210ULL; // ULLサフィックス

    printf("Exact 32bit signed: %d\n", exact_32bit_signed);
    // uint64_t を printf で表示する際は  のマクロを使うのが推奨される
    // ここでは例としてキャストを使用 (環境によっては警告や問題が出る可能性あり)
    printf("Exact 64bit unsigned: %llu\n", (unsigned long long)exact_64bit_unsigned);

    return 0;
}

整数リテラル

コード中に直接記述する整数値の表現方法です。

  • 接尾辞 (Suffix):
    • u または U: 符号無し (unsigned) を示す。例: 100U
    • l または L: long 型を示す。例: 123456L
    • ll または LL: long long 型を示す (C99以降)。例: 9876543210LL
    • 組み合わせ可能: 例: 50000UL, 10000000000ULL
  • 基数 (Radix):
    • 接頭辞なし: 10進数 (例: 123)
    • 0: 8進数 (例: 0173 は10進数の 123)
    • 0x または 0X: 16進数 (例: 0x7B は10進数の 123)
    • 0b または 0B: 2進数 (C++14以降の機能だが、一部のCコンパイラ拡張で利用可能。標準Cではない) (例: 0b01111011)
unsigned int ui = 4000000000U;
long       l  = -100000L;
long long  ll = 5000000000LL;

int octal_val = 077;  // 10進数で 63
int hex_val   = 0xFF; // 10進数で 255
// int bin_val   = 0b1010; // 標準Cではない (コンパイラ拡張)

実数を扱うための型

小数部分を持つ数値を格納するためのデータ型です。精度と格納できる値の範囲によって種類があります。一般的に IEEE 754 標準規格に準拠しています。

型名 説明 一般的なサイズ 一般的な有効桁数 (10進) 一般的な値の範囲 (絶対値)
float 単精度浮動小数点数。メモリ使用量は少ないが精度は低い。 32ビット (IEEE 754 single) 約6〜7桁 約 1.2E-38 〜 3.4E+38
double 倍精度浮動小数点数。float より精度が高く、一般的に使われる。 64ビット (IEEE 754 double) 約15〜16桁 約 2.2E-308 〜 1.8E+308
long double 拡張倍精度浮動小数点数。処理系によって精度が大きく異なる (double と同じ場合もある)。 80ビット (Intel形式) や 128ビット (IEEE 754 quadruple) など、様々 (64ビットの場合も) double 以上 (例: 約18〜19桁, 33〜34桁) double 以上

⚠️ 注意: 浮動小数点数は近似値であるため、誤差が発生する可能性があります。特に等価比較 (==) は意図通りに動作しないことがあるため、微小な許容範囲 (epsilon) を設けて比較するなどの工夫が必要です。

<float.h> ヘッダで定義されるマクロ (例: FLT_DIG, DBL_MAX, LDBL_EPSILON) を使うことで、実行環境での正確な特性を確認できます。

浮動小数点リテラル

  • デフォルトでは double 型として扱われます。
  • 接尾辞 (Suffix):
    • f または F: float 型を示す。例: 3.14f
    • l または L: long double 型を示す。例: 1.23456789L
  • 表記法:
    • 小数点表記: 例: 123.45, 0.1, .5 (0.5と同じ)
    • 指数表記 (E表記): 例: 1.23e4 (1.23 × 10⁴), 5E-3 (5 × 10⁻³)
    • 16進浮動小数点リテラル (C99以降): 0x または 0X で始まり、指数部は p または P を使う。例: 0x1.9p+3 (1.5625 × 2³)
#include <stdio.h>
#include <float.h> // FLT_DIG, DBL_DIG など

int main(void) {
    float  pi_f = 3.14159265f;
    double pi_d = 3.141592653589793;
    long double pi_ld = 3.14159265358979323846L;

    float scientific_f = 1.23e-5f; // 0.0000123
    double hex_fp = 0x1.ap+0;      // (1 + 10/16) * 2^0 = 1.625

    printf("float precision (FLT_DIG): %d\n", FLT_DIG);
    printf("double precision (DBL_DIG): %d\n", DBL_DIG);
    printf("long double precision (LDBL_DIG): %d\n", LDBL_DIG);

    printf("pi_f = %.8f\n", pi_f);   // 精度の限界が見えることがある
    printf("pi_d = %.16f\n", pi_d);
    printf("pi_ld = %.20Lf\n", pi_ld); // %Lf で long double を表示
    printf("scientific_f = %f\n", scientific_f);
    printf("hex_fp = %f\n", hex_fp);

    // 誤差の例
    if (0.1 + 0.2 == 0.3) {
        printf("0.1 + 0.2 is equal to 0.3\n"); // 通常、これは表示されない
    } else {
        printf("0.1 + 0.2 is NOT equal to 0.3 (due to precision errors)\n");
    }

    return 0;
}

文字データを扱う型

char 型

1バイトの整数型であり、通常はASCII文字などの基本的な文字セットの文字を格納するために使用されます。

  • 文字リテラルはシングルクォーテーション (') で囲みます。例: 'A', '1', '\n' (改行文字)
  • 内部的には文字コード (整数値) として格納されます。
  • 文字列 (複数の文字の並び) は、char 型の配列やポインタを使って表現します (ヌル文字 \0 で終端)。
#include <stdio.h>

int main(void) {
    char grade = 'A';
    char initial = 'T';
    char newline = '\n'; // エスケープシーケンス

    printf("Grade: %c\n", grade);       // %c で文字として表示
    printf("Initial: %c\n", initial);
    printf("ASCII value of 'A': %d\n", grade); // %d で整数値 (文字コード) として表示

    // 文字列 (char 配列)
    char message[] = "Hello, World!"; // 暗黙的に末尾に '\0' が追加される
    char *ptr_message = "Another message"; // 文字列リテラルへのポインタ

    printf("%s\n", message); // %s で文字列を表示
    printf("%s\n", ptr_message);

    return 0;
}

ワイド文字型 (wchar_t, <wchar.h>)

マルチバイト文字やUnicode文字など、char 型では表現しきれない広範囲な文字セットを扱うための型です (C95以降)。サイズは処理系定義です。

  • ワイド文字リテラルは L 接頭辞をつけ、シングルクォーテーションで囲みます。例: L'あ'
  • ワイド文字列リテラルも L 接頭辞をつけ、ダブルクォーテーションで囲みます。例: L"日本語"
  • ワイド文字/文字列を扱うための関数群が <wchar.h><wctype.h> で提供されます (例: wprintf, wcslen)。
  • ロケール設定 (setlocale) が適切に行われている必要があります。
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(void) {
    // ロケールを設定しないと、ワイド文字が正しく表示されないことがある
    setlocale(LC_ALL, ""); // 環境のデフォルトロケールを使用

    wchar_t wide_char = L'猫'; // ワイド文字 '猫'
    wchar_t wide_str[] = L"こんにちは、世界!"; // ワイド文字列

    // wprintf はワイド文字/文字列を出力するための関数 (%lc, %ls)
    wprintf(L"ワイド文字: %lc\n", wide_char);
    wprintf(L"ワイド文字列: %ls\n", wide_str);
    wprintf(L"wchar_t のサイズ: %zu バイト\n", sizeof(wchar_t));

    return 0;
}

💡 C11では、UTF-16用の char16_t (リテラル接頭辞 u) と UTF-32用の char32_t (リテラル接頭辞 U) も導入されました。これらは <uchar.h> ヘッダで使用可能です。

真偽値を扱う型 (C99以降)

C99で導入された、真 (true) または偽 (false) の値を格納するための型です。<stdbool.h> ヘッダをインクルードして使用します。

  • 型名: _Bool (キーワード)
  • ヘッダ: <stdbool.h> をインクルードすると、bool, true, false というマクロが定義され、より直感的に利用できます。
  • 値:
    • true: 通常は 1
    • false: 0
  • _Bool 型 (または bool) に 0 以外の値を代入すると true (1) になり、0 を代入すると false (0) になります。
  • 条件式 (if, while など) では、0 が偽、0 以外が真として扱われるC言語の伝統的な挙動は変わりません。
#include <stdio.h>
#include <stdbool.h> // bool, true, false を使うために必要

int main(void) {
    bool is_active = true;
    bool has_error = false;
    bool condition = (5 > 3); // true になる

    printf("is_active: %d\n", is_active); // 通常 1 が表示される
    printf("has_error: %d\n", has_error); // 通常 0 が表示される
    printf("condition: %d\n", condition); // 通常 1 が表示される

    if (is_active) {
        printf("System is active.\n");
    }

    if (!has_error) { // ! は否定演算子
        printf("No errors found.\n");
    }

    // 0以外の値を代入すると true (1) になる
    bool test_val = -100;
    printf("test_val (assigned -100): %d\n", test_val); // 1 が表示される

    return 0;
}

型が存在しないことを示す特殊な型

void は、値を持たない、または型が未定であることを示す特殊な型指定子です。オブジェクトを宣言することはできません。

  • 関数の戻り値の型: 関数が値を返さないことを示します。
    void print_message(const char *msg) {
        printf("%s\n", msg);
        // return; は不要 (あっても良い)
    }
  • 関数の仮引数リスト: 関数が引数を取らないことを明示します (C言語特有)。int func() は引数が不定であることを意味しますが、int func(void) は引数を取らないことを明確に示します。
    int get_random_number(void) {
        // 何らかの処理
        return 42; // 例
    }
  • 汎用ポインタ (void*): あらゆる型のオブジェクトのアドレスを格納できるポインタです。使用する際には、元の型や互換性のある型にキャストする必要があります。
    #include <stdio.h>
    
    int main(void) {
        int number = 10;
        float pi = 3.14f;
        char initial = 'Z';
    
        void *generic_ptr;
    
        generic_ptr = &number;
        // 使用する前に適切な型にキャストする
        printf("Value via void* (int): %d\n", *(int*)generic_ptr);
    
        generic_ptr = &pi;
        printf("Value via void* (float): %f\n", *(float*)generic_ptr);
    
        generic_ptr = &initial;
        printf("Value via void* (char): %c\n", *(char*)generic_ptr);
    
        return 0;
    }
    

    ⚠️ void* ポインタは型情報を失うため、キャストする型を間違えると未定義動作を引き起こす可能性があります。注意して使用する必要があります。

基本データ型を組み合わせて作られる型

配列 (Array)

同じ型の要素を連続したメモリ領域に格納するデータ構造です。

  • 宣言: 型名 配列名[要素数]; 例: int scores[10]; (10個のint型要素を持つ配列)
  • 初期化:
    • int numbers[5] = {1, 2, 3, 4, 5};
    • char greeting[] = "Hi"; (要素数は3: ‘H’, ‘i’, ‘\0’)
    • int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}}; (多次元配列)
    • 初期化子を指定しない場合、静的記憶域期間を持つ場合は0で、自動記憶域期間を持つ場合は不定値で初期化されます。
  • アクセス: 配列名[インデックス] (インデックスは0から始まる) 例: scores[0], matrix[1][2]
  • 配列名は、多くの場合、配列の先頭要素へのポインタとして扱われます。
#include <stdio.h>

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};
    size_t i; // size_t は配列のサイズやインデックスに適した符号無し整数型

    data[1] = 25; // 2番目の要素を変更

    printf("Array elements: ");
    for (i = 0; i < 5; i++) {
        printf("%d ", data[i]); // 各要素にアクセス
    }
    printf("\n");

    printf("Size of array data: %zu bytes\n", sizeof(data));
    printf("Number of elements: %zu\n", sizeof(data) / sizeof(data[0]));

    return 0;
}

ポインタ (Pointer) 👉

他の変数のメモリアドレスを格納するための変数です。

  • 宣言: 型名 *ポインタ変数名; 例: int *ptr; (int型変数へのポインタ)
  • アドレス取得演算子 (&): 変数のアドレスを取得します。例: ptr = &my_var;
  • 間接参照演算子 (*): ポインタが指すアドレスにある値にアクセスします。例: value = *ptr;
  • NULLポインタ: どこも指していないことを示す特別なポインタ値 (通常は NULL マクロ (<stddef.h> など) や 0 で表現)。
  • ポインタ演算: ポインタに対して加減算を行うと、指し示す型サイズ単位でアドレスが移動します。
#include <stdio.h>
#include <stddef.h> // NULL の定義

int main(void) {
    int value = 100;
    int *ptr_to_value = NULL; // NULLで初期化する習慣が良い

    ptr_to_value = &value; // value のアドレスを ptr_to_value に格納

    printf("Address of value: %p\n", (void*)&value); // %p でアドレス表示 (void*にキャスト推奨)
    printf("Value stored in ptr_to_value: %p\n", (void*)ptr_to_value);
    printf("Value pointed to by ptr_to_value: %d\n", *ptr_to_value); // 間接参照

    *ptr_to_value = 200; // ポインタ経由で value の値を変更
    printf("New value of value: %d\n", value);

    // ポインタ演算
    int numbers[] = {10, 20, 30};
    int *p = numbers; // 配列名は先頭要素へのポインタ
    printf("First element: %d\n", *p);
    p++; // 次の要素 (int型サイズ分アドレスが進む)
    printf("Second element: %d\n", *p);

    return 0;
}

構造体 (Structure) 🧱

異なる型のデータをひとまとめにして扱うためのユーザー定義型です。

  • 定義:
    struct タグ名 {
        型名 メンバ名1;
        型名 メンバ名2;
        // ...
    };
  • 宣言: struct タグ名 変数名; 例: struct Point p1;
  • typedef を使うと、より簡潔に宣言できます:
    typedef struct {
        double x;
        double y;
    } Point;
    
    Point p2; // typedef を使うと struct を省略できる
  • メンバへのアクセス:
    • ドット演算子 (.): 構造体変数からメンバにアクセス。例: p1.x
    • アロー演算子 (->): 構造体へのポインタからメンバにアクセス。例: ptr_p->y ((*ptr_p).y と等価)
  • 初期化: struct Point p = {1.0, 2.5}; (定義順) / struct Point p = {.y = 2.5, .x = 1.0}; (指示付き初期化子, C99)
#include <stdio.h>
#include <string.h>

// 構造体の定義
struct Person {
    char name[50];
    int age;
    float height;
};

// typedef を使った定義
typedef struct {
    char title[100];
    char author[100];
    int year;
} Book;

int main(void) {
    // 構造体変数の宣言と初期化
    struct Person person1 = {"Alice", 30, 165.5f};
    struct Person person2;

    // メンバへの代入 (ドット演算子)
    strcpy(person2.name, "Bob");
    person2.age = 25;
    person2.height = 175.0f;

    printf("Person 1: Name=%s, Age=%d, Height=%.1f\n",
           person1.name, person1.age, person1.height);

    // 構造体へのポインタ
    struct Person *ptr_person = &person2;

    // メンバへのアクセス (アロー演算子)
    printf("Person 2: Name=%s, Age=%d, Height=%.1f\n",
           ptr_person->name, ptr_person->age, ptr_person->height);

    // typedef を使った変数宣言と指示付き初期化子 (C99)
    Book book1 = {.title = "The C Programming Language", .author = "K&R", .year = 1978};
    printf("Book: %s by %s (%d)\n", book1.title, book1.author, book1.year);

    return 0;
}

共用体 (Union) 🔀

複数のメンバが同じメモリ領域を共有するユーザー定義型です。一度に有効なメンバは一つだけです。

  • 定義: struct と同様の構文ですが、union キーワードを使います。
    union Data {
        int i;
        float f;
        char str[20];
    };
  • サイズ: 共用体のサイズは、最も大きいメンバのサイズ以上になります (アライメント要件によるパディングを含む場合がある)。
  • アクセス: 構造体と同様にドット演算子 (.) またはアロー演算子 (->) を使用します。
  • 注意点: あるメンバに値を代入した後、別のメンバから値を読み出すと、その動作は処理系定義または未定義となることがあります (型パンニング)。最後に書き込んだメンバを通してアクセスするのが基本です。
#include <stdio.h>
#include <string.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main(void) {
    union Data data;

    printf("Size of union Data: %zu bytes\n", sizeof(data));

    data.i = 10;
    printf("data.i: %d\n", data.i);
    // この時点で data.f や data.str を読み取るのは危険

    data.f = 220.5f;
    printf("data.f: %f\n", data.f);
    // この時点で data.i を読み取るのは危険

    strcpy(data.str, "Hello");
    printf("data.str: %s\n", data.str);
    // この時点で data.i や data.f を読み取るのは危険

    // 最後に書き込んだメンバを通してアクセスするのが安全
    printf("Accessing str again: %s\n", data.str);

    return 0;
}

列挙型 (Enumeration) 🏷️

関連する整数定数に名前(列挙子)をつけるためのユーザー定義型です。

  • 定義:
    enum タグ名 {
        列挙子1, // デフォルトで 0
        列挙子2, // デフォルトで 1
        列挙子3 = 10, // 値を明示的に指定可能
        列挙子4 // 前の列挙子 + 1 なので 11
    };
  • 宣言: enum タグ名 変数名; 例: enum Color selected_color;
  • typedef も利用可能です。
  • 列挙子は int 型の定数として扱われます。コードの可読性を向上させます。
#include <stdio.h>

// 列挙型の定義
enum Weekday {
    SUNDAY,    // 0
    MONDAY,    // 1
    TUESDAY,   // 2
    WEDNESDAY, // 3
    THURSDAY,  // 4
    FRIDAY,    // 5
    SATURDAY   // 6
};

typedef enum {
    RED,
    GREEN,
    BLUE
} Color;

int main(void) {
    enum Weekday today = WEDNESDAY;
    Color my_color = BLUE;

    if (today == WEDNESDAY) {
        printf("Today is Wednesday (Value: %d)\n", today);
    }

    switch (my_color) {
        case RED:
            printf("Selected color is Red (%d)\n", my_color);
            break;
        case GREEN:
            printf("Selected color is Green (%d)\n", my_color);
            break;
        case BLUE:
            printf("Selected color is Blue (%d)\n", my_color);
            break;
        default:
            printf("Unknown color\n");
    }

    printf("Value of SUNDAY: %d\n", SUNDAY);
    printf("Value of SATURDAY: %d\n", SATURDAY);

    return 0;
}

型の特性を変更・追加するキーワード

const

変数の値が変更されないことを示します。読み取り専用にします。

  • const int MAX_VALUE = 100; : MAX_VALUE の値を変更しようとするとコンパイルエラーになります。
  • ポインタとの組み合わせ:
    • const int *ptr; : ポインタが指す先の値が変更不可 (ptr 自体は変更可能)。
    • int * const ptr; : ポインタ変数 ptr 自体が変更不可 (指す先の値は変更可能)。初期化が必須。
    • const int * const ptr; : ポインタ変数も、その指す先の値も変更不可。初期化が必須。
  • 関数引数で const を使うと、関数内でその引数を変更しないことを保証できます (特にポインタや配列の場合に有用)。
#include <stdio.h>

void print_array(const int arr[], size_t size) {
    // arr[0] = 100; // コンパイルエラー (const修飾されているため)
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    const float PI = 3.14159f;
    // PI = 3.14; // コンパイルエラー

    int value = 10;
    const int *ptr_to_const_int = &value;
    // *ptr_to_const_int = 20; // コンパイルエラー
    value = 30; // これはOK
    int another_value = 40;
    ptr_to_const_int = &another_value; // これもOK

    int * const const_ptr = &value;
    *const_ptr = 50; // これはOK
    // const_ptr = &another_value; // コンパイルエラー

    const int * const const_ptr_to_const_int = &value;
    // *const_ptr_to_const_int = 60; // コンパイルエラー
    // const_ptr_to_const_int = &another_value; // コンパイルエラー

    int data[] = {1, 2, 3};
    print_array(data, 3);

    return 0;
}

volatile

変数の値が、プログラムの制御外 (例: ハードウェア、割り込み、別スレッド) で変更される可能性があることをコンパイラに伝えます。これにより、コンパイラはその変数へのアクセスに関する最適化 (レジスタへのキャッシュなど) を抑制します。

  • メモリマップドI/Oレジスタへのアクセスによく使われます。
  • 割り込みサービスルーチンとメインコード間で共有される変数。
  • マルチスレッド環境で、複数のスレッドからアクセスされる変数 (ただし、アトミック操作や排他制御が必要な場合は _Atomic やミューテックス等を使うべき)。
// ハードウェアレジスタの例 (アドレスは仮)
volatile unsigned int *hardware_status_register = (volatile unsigned int *)0x12345678;

void check_device_status() {
    // volatile がないと、コンパイラがループ内でレジスタ値を
    // 再読み込みしない最適化を行う可能性がある
    while ((*hardware_status_register & 0x01) == 0) {
        // デバイスが準備できるまで待機
    }
    // 準備完了後の処理
}

// 割り込みと共有される変数の例
volatile int interrupt_flag = 0;

void interrupt_handler() {
    // ... 割り込み処理 ...
    interrupt_flag = 1; // 割り込み内でフラグを設定
}

int main(void) {
    // ... 初期化 ...
    while (interrupt_flag == 0) {
        // volatile がないと、コンパイラはこのループを最適化で
        // 無限ループにしてしまう可能性がある (フラグが変化しないと判断するため)
        // 何らかの待機処理 or 他の処理
    }
    printf("Interrupt occurred!\n");
    interrupt_flag = 0; // フラグをリセット
    // ... 割り込み後の処理 ...
    return 0;
}

restrict (C99以降)

ポインタに対して使用され、そのポインタが、特定のオブジェクトにアクセスするための唯一の初期手段であることをコンパイラに約束します。これにより、コンパイラはより積極的な最適化 (エイリアシングを考慮しない最適化) を行うことができます。

  • 主に関数パラメータのポインタに対して使用されます。
  • void *memcpy(void * restrict dest, const void * restrict src, size_t n); のように、コピー元とコピー先が重ならないことを示唆します。
  • プログラマが条件を保証する必要があり、違反すると未定義動作を引き起こします。
#include <stdio.h>

// この関数は、out, in1, in2 が互いに重ならないメモリ領域を指すことを期待する
void add_arrays(float * restrict out, const float * restrict in1, const float * restrict in2, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        out[i] = in1[i] + in2[i]; // コンパイラは out[i] の書き込みが in1 や in2 の読み込みに影響しないと仮定できる
    }
}

int main(void) {
    float a[] = {1.0f, 2.0f, 3.0f};
    float b[] = {4.0f, 5.0f, 6.0f};
    float result[3];

    add_arrays(result, a, b, 3);

    printf("Result: ");
    for (int i = 0; i < 3; ++i) {
        printf("%.1f ", result[i]);
    }
    printf("\n"); // 出力: Result: 5.0 7.0 9.0

    // restrict の制約に違反する例 (未定義動作)
    // add_arrays(a, a, b, 3); // out と in1 が同じメモリを指す

    return 0;
}

_Atomic (C11以降)

アトミック操作 (不可分操作) が保証される型を指定します。マルチスレッド環境で、ロックなしで安全に共有変数にアクセスするために使用されます。<stdatomic.h> ヘッダと関連関数 (atomic_fetch_add など) と共に使うのが一般的です。

  • _Atomic int counter; のように宣言します。
  • <stdatomic.h> をインクルードすると、atomic_int のような型名や atomic_load, atomic_store, atomic_fetch_add などのアトミック操作関数が利用可能になります。
// C11 以降で、対応するコンパイラとライブラリが必要
#include <stdio.h>
#include <stdatomic.h> // atomic_* 型や関数
#include <threads.h>   // thrd_* 関数 (スレッド操作)

atomic_int shared_counter = 0; // アトミックな整数カウンター

// スレッドで実行される関数
int worker(void *arg) {
    (void)arg; // 未使用引数の警告抑制
    for (int i = 0; i < 100000; ++i) {
        // アトミックにカウンターをインクリメント
        atomic_fetch_add(&shared_counter, 1);
    }
    return 0;
}

int main(void) {
    thrd_t threads[5];
    int i;

    printf("Initial counter value: %d\n", atomic_load(&shared_counter));

    // 5つのスレッドを作成してカウンターをインクリメントさせる
    for (i = 0; i < 5; ++i) {
        if (thrd_create(&threads[i], worker, NULL) != thrd_success) {
            fprintf(stderr, "Error creating thread %d\n", i);
            return 1;
        }
    }

    // 全てのスレッドが終了するのを待つ
    for (i = 0; i < 5; ++i) {
        thrd_join(threads[i], NULL);
    }

    // 最終的なカウンターの値を取得して表示
    // データ競合なしに正しい値 (500000) が得られるはず
    printf("Final counter value: %d\n", atomic_load(&shared_counter));

    return 0;
}

あるデータ型を別のデータ型として扱う

暗黙の型変換 (Implicit Conversion)

コンパイラが自動的に行う型変換です。主に以下の状況で発生します。

  • 整数拡張 (Integer Promotion): char, short, ビットフィールドなどの int より小さい整数型は、式の中で評価される際に int または unsigned int に変換されます。
    char c1 = 10, c2 = 20;
    int result = c1 + c2; // c1 と c2 は int に拡張されてから加算される
    
  • 通常の算術変換 (Usual Arithmetic Conversions): 異なる算術型 (整数型、浮動小数点型) を持つオペランドを持つ二項演算で、オペランドの型を共通の型に揃えるために行われます。
    • 一方が浮動小数点型なら、もう一方も同じ浮動小数点型 (より精度の高い方に合わせる) に変換される。例: int + doubledouble + double
    • 両方とも整数型なら、整数拡張の後、ランク (大まかにはビット幅) が高い方に合わせる。符号付きと符号無しが混在する場合は、符号無しの方がランクが高いか同じなら符号無しに、そうでなければ符号付きの方の型に変換される (詳細は複雑)。
      int i = 10;
      double d = 3.14;
      double sum = i + d; // i が double に変換されてから加算
      
      unsigned int ui = 100U;
      long l = -50L;
      // 環境によるが、多くの場合 ui が long に変換されるか、l が unsigned long に変換される
      // (結果の型は unsigned long になる可能性が高い)
      // if (ui > l) のような比較は意図しない結果になることがあるので注意!
      
  • 代入時: 代入演算子の右辺の値が左辺の変数の型に変換されます。情報が失われる可能性があります (例: doubleint に代入すると小数部が切り捨てられる)。
    int integer_part;
    double real_number = 123.45;
    integer_part = real_number; // integer_part には 123 が格納される (小数部切り捨て)
    
  • 関数呼び出し時: 関数プロトタイプがない場合や可変長引数 (…) に渡される実引数は「デフォルト実引数拡張」が行われます (floatdouble に、整数型は整数拡張)。プロトタイプがある場合は、仮引数の型に変換されます。

⚠️ 暗黙の型変換、特に符号付きと符号無しの間の変換や、浮動小数点数から整数への変換は、予期しない結果やバグの原因となることがあるため、注意が必要です。

明示的な型変換 (Explicit Conversion / Casting)

プログラマが意図的に型を変換する操作です。キャスト演算子 (型名) を使います。

  • 構文: (変換先の型名) 式
  • 例:
    • int a = 10, b = 3; double avg = (double)a / b; : 整数除算を避けるために adouble にキャストし、浮動小数点除算を行う。
    • void *ptr = malloc(100); int *int_ptr = (int *)ptr; : void* 型のポインタを特定の型 (int*) にキャストする。
    • float f = 12.9f; int i = (int)f; : 浮動小数点数を整数にキャスト (小数部切り捨て)。
  • 暗黙の変換と同様に、情報が失われる可能性があります。
  • キャストは強力ですが、型の安全性を損なう可能性があるため、必要な場合にのみ慎重に使用するべきです。
#include <stdio.h>
#include <stdlib.h> // malloc のため

int main(void) {
    int total_score = 150;
    int num_students = 7;

    // キャストしないと整数除算になり、結果は 21 になる
    // double average_score = total_score / num_students;
    // printf("Average (int division): %f\n", average_score);

    // どちらか一方を double にキャストして浮動小数点除算を行う
    double average_score_cast = (double)total_score / num_students;
    printf("Average (float division): %f\n", average_score_cast); // 約 21.428571

    double pi = 3.14159;
    int integer_pi = (int)pi; // キャストによる切り捨て
    printf("Integer part of pi: %d\n", integer_pi); // 3

    // void* から具体的なポインタ型へのキャスト
    void *mem = malloc(sizeof(int));
    if (mem == NULL) return 1; // malloc失敗チェック

    int *int_ptr = (int *)mem; // void* を int* にキャスト
    *int_ptr = 12345;
    printf("Value via casted pointer: %d\n", *int_ptr);

    free(mem); // 確保したメモリを解放

    return 0;
}

データ型のメモリ上での大きさと配置

sizeof 演算子

オブジェクトや型のメモリ上でのサイズ (バイト単位) を取得します。

  • sizeof(型名) : 型のサイズを取得。例: sizeof(int)
  • sizeof 式 : 式の評価結果の型のサイズを取得 (式自体は評価されないことが多い)。例: sizeof(variable), sizeof(1 + 2.0) (doubleのサイズになる)
  • 戻り値の型は size_t (符号無し整数型, <stddef.h> で定義)。printf で表示する際は %zu 書式指定子を使います。
  • 配列に適用すると、配列全体のサイズを返します。ポインタに適用すると、ポインタ自体のサイズを返します。
#include <stdio.h>
#include <stddef.h> // size_t

int main(void) {
    char c;
    int i;
    double d;
    int arr[10];
    int *ptr = arr;

    printf("Size of char: %zu byte(s)\n", sizeof(char));
    printf("Size of int: %zu byte(s)\n", sizeof(int));
    printf("Size of double: %zu byte(s)\n", sizeof(d)); // 変数名でもOK
    printf("Size of int[10] array: %zu byte(s)\n", sizeof(arr));
    printf("Size of int pointer: %zu byte(s)\n", sizeof(ptr));
    printf("Size of expression (1 + 2.0): %zu byte(s)\n", sizeof(1 + 2.0)); // double のサイズ

    return 0;
}

アライメント (Alignment)

多くのプロセッサは、特定のデータ型をメモリ上の特定のアドレス(例えば、4バイト境界や8バイト境界など)に配置することで、より効率的にアクセスできます。この配置要件をアライメントと呼びます。

  • 通常、コンパイラは自動的にアライメント要件を満たすように変数を配置し、構造体メンバ間にパディング(隙間)を挿入することがあります。
  • 不適切なアライメントは、パフォーマンス低下や、環境によっては実行時エラーを引き起こす可能性があります。

_Alignof 演算子 (C11以降)

型の推奨アライメント(バイト単位)を取得します。sizeof と同様の構文で使用します。

  • _Alignof(型名) : 型のアライメントを取得。例: _Alignof(double)
  • <stdalign.h> をインクルードすると、alignof というマクロ名で利用できます。
  • 戻り値の型は size_t です。

_Alignas 指定子 (C11以降)

変数のアライメントを明示的に指定します。

  • _Alignas(アライメント値) 型名 変数名; または _Alignas(型名) 型名 変数名;
    • アライメント値は、定数式または型名で指定します。通常、2のべき乗です。
    • 指定されたアライメントは、その型のデフォルトアライメント以上でなければなりません(より厳しいアライメントを指定することはできますが、緩めることはできません)。
  • <stdalign.h> をインクルードすると、alignas というマクロ名で利用できます。
  • 特定のメモリアドレスに配置したい場合や、SIMD命令などで特定のアライメントが必要な場合に使用されます。
// C11 以降
#include <stdio.h>
#include <stddef.h>  // size_t
#include <stdalign.h> // alignof, alignas

// 構造体内のパディングの例
struct Example {
    char c1;     // 1 byte + 3 bytes padding?
    int i;       // 4 bytes
    char c2;     // 1 byte + 7 bytes padding?
    double d;    // 8 bytes
};

int main(void) {
    printf("Alignment of char: %zu\n", alignof(char));
    printf("Alignment of int: %zu\n", alignof(int));
    printf("Alignment of double: %zu\n", alignof(double));
    printf("Alignment of struct Example: %zu\n", alignof(struct Example)); // 通常、最も厳しいメンバ(double)のアライメントに等しい

    printf("Size of struct Example: %zu\n", sizeof(struct Example)); // パディングが含まれるため、メンバサイズの単純合計 (1+4+1+8=14) より大きいことが多い (例: 24)

    // アライメントを明示的に指定
    alignas(16) int aligned_int_array[4]; // 配列全体が16バイト境界に配置されることを要求
    alignas(double) char cache_line[64]; // double のアライメントに合わせる

    printf("Alignment of aligned_int_array: %zu\n", alignof(aligned_int_array));
    printf("Address of aligned_int_array: %p\n", (void*)aligned_int_array); // アドレスが16の倍数になっているはず

    // より厳しいアライメントを持つ変数をスタック上に確保
    alignas(32) struct Example aligned_struct_var;
    printf("Alignment of aligned_struct_var: %zu\n", alignof(aligned_struct_var));
    printf("Address of aligned_struct_var: %p\n", (void*)&aligned_struct_var);

    return 0;
}

コメント

タイトルとURLをコピーしました