コンテンツにスキップ

C言語/sizeof

出典: フリー教科書『ウィキブックス(Wikibooks)』

はじめに

[編集]

sizeof演算子はC言語において、型やオブジェクトのサイズをバイト単位で取得するための基本的な機能です。この演算子はコンパイル時に評価され、プログラムの実行時にはすでに定数として扱われます。C23(ISO/IEC 9899:2024)においてもsizeofの基本的な機能は維持されつつ、いくつかの改善が加えられています。

sizeofの基本概念

[編集]

sizeof演算子は、オペランドのサイズをバイト単位で返します。オペランドには型名または式を指定することができます。

#include <stdio.h>
int main() {
    int i;
    double d;
    char c;
    
    printf("int型のサイズ: %zu バイト\n", sizeof(int));
    printf("double型のサイズ: %zu バイト\n", sizeof(double));
    printf("char型のサイズ: %zu バイト\n", sizeof(char));
    
    printf("変数iのサイズ: %zu バイト\n", sizeof i);  // 括弧は省略可能
    printf("変数dのサイズ: %zu バイト\n", sizeof d);
    printf("変数cのサイズ: %zu バイト\n", sizeof c);
    
    return 0;
}

この例では、sizeof演算子を使用して基本データ型と変数のサイズを取得しています。sizeofの結果はsize_t型(符号なし整数型)で返されるため、出力時には%zu形式指定子を使用します。

sizeofの文法

[編集]

sizeof演算子は、次の2つの形式で使用できます:

  1. sizeof expression
  2. sizeof(type-name)

式の場合、括弧は任意ですが、型名の場合は必須です。

#include <stdio.h>
int main() {
    // 式に対するsizeof(括弧は任意)
    int arr[10];
    printf("配列全体のサイズ: %zu バイト\n", sizeof arr);
    printf("配列の要素数: %zu\n", sizeof arr / sizeof arr[0]);
    
    // 型名に対するsizeof(括弧は必須)
    printf("long double型のサイズ: %zu バイト\n", sizeof(long double));
    
    return 0;
}

sizeofと式の評価

[編集]

sizeof演算子の重要な特性は、オペランドが式の場合でも実際にその式を評価しないということです。

#include <stdio.h>
int increment(int* value) {
    (*value)++;
    return *value;
}

int main() {
    int counter = 0;
    
    // sizeof演算子は式を評価しない
    size_t size = sizeof(increment(&counter));
    
    printf("counterの値: %d\n", counter);  // 0と表示される
    printf("int型のサイズ: %zu バイト\n", size);
    
    return 0;
}

この例では、increment(&counter)という式がsizeof演算子のオペランドとして使用されていますが、実際には評価されないため、counterの値は変化しません。

sizeofの利用例

[編集]

配列の要素数の計算

[編集]

sizeof演算子を使用して、配列の要素数を取得できます:

#include <stdio.h>
void print_array(int arr[], size_t size) {
    for (size_t i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    size_t count = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("配列の要素数: %zu\n", count);
    print_array(numbers, count);
    
    return 0;
}

メモリ割り当て

[編集]

sizeof演算子は、動的メモリ割り当ての際にサイズを計算するために頻繁に使用されます:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
    char name[50];
    int age;
    double salary;
} Employee;

int main() {
    // 単一のEmployeeを割り当て
    Employee* emp = malloc(sizeof(Employee));
    if (emp == NULL) {
        fprintf(stderr, "メモリ割り当てエラー\n");
        return 1;
    }
    
    strcpy(emp->name, "山田太郎");
    emp->age = 30;
    emp->salary = 450000.0;
    
    printf("従業員: %s, %d歳, 給与 %.2f円\n", 
           emp->name, emp->age, emp->salary);
    
    // 複数のEmployeeを割り当て
    int num_employees = 5;
    Employee* employees = malloc(sizeof(Employee) * num_employees);
    if (employees == NULL) {
        fprintf(stderr, "メモリ割り当てエラー\n");
        free(emp);
        return 1;
    }
    
    printf("割り当てられたメモリ: %zu バイト\n", 
           sizeof(Employee) * num_employees);
    
    free(emp);
    free(employees);
    return 0;
}

構造体のサイズとアライメント

[編集]

sizeof演算子を使用して構造体のサイズを取得すると、構造体のメンバのサイズだけでなく、メモリアライメントのためのパディングも含まれることに注意が必要です:

#include <stdio.h>
struct Example1 {
    char c;    // 1バイト
    int i;     // 4バイト
    double d;  // 8バイト
};

struct Example2 {
    int i;     // 4バイト
    double d;  // 8バイト
    char c;    // 1バイト
};

struct Packed {
    char c;    // 1バイト
    int i;     // 4バイト
    double d;  // 8バイト
} __attribute__((packed));  // パッキング属性

int main() {
    printf("Example1構造体のサイズ: %zu バイト\n", sizeof(struct Example1));
    printf("Example2構造体のサイズ: %zu バイト\n", sizeof(struct Example2));
    printf("Packed構造体のサイズ: %zu バイト\n", sizeof(struct Packed));
    
    printf("\n各メンバのサイズの合計:\n");
    printf("Example1: %zu バイト\n", sizeof(char) + sizeof(int) + sizeof(double));
    
    printf("\n各メンバのオフセット (Example1):\n");
    printf("c: %zu\n", offsetof(struct Example1, c));
    printf("i: %zu\n", offsetof(struct Example1, i));
    printf("d: %zu\n", offsetof(struct Example1, d));
    
    return 0;
}

この例では、メンバの順序によって構造体の合計サイズが変わることを示しています。これはメモリアライメントの要件によるものです。

可変長配列(VLA)のsizeof

[編集]

C99以降では、可変長配列(VLA)のサイズを取得するためにsizeofを使用することができます。この場合、sizeofの評価は実行時に行われます:

#include <stdio.h>
void process_array(int size) {
    // 可変長配列の宣言
    int vla[size];
    
    // 実行時にサイズが計算される
    size_t array_size = sizeof(vla);
    
    printf("VLAのサイズ: %zu バイト\n", array_size);
    printf("要素数: %zu\n", array_size / sizeof(int));
    
    for (int i = 0; i < size; i++) {
        vla[i] = i * 10;
    }
    
    printf("VLAの内容: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", vla[i]);
    }
    printf("\n");
}

int main() {
    int size;
    
    printf("配列のサイズを入力してください: ");
    scanf("%d", &size);
    
    process_array(size);
    
    return 0;
}

C23では、可変長配列の使用に関する警告が強化されていますが、基本的な機能は維持されています。

C23におけるsizeofの改善

[編集]

C23では、sizeof演算子に関するいくつかの改善が含まれています:

  1. 複合リテラルに対するsizeofの挙動の明確化
  2. ビットフィールドを含む構造体に対するsizeofの挙動の明確化
  3. 可変長配列の型に対するsizeofの使用に関する警告の強化
#include <stdio.h>
struct BitField {
    unsigned int a : 3;  // 3ビット
    unsigned int b : 5;  // 5ビット
    unsigned int c : 4;  // 4ビット
};

int main() {
    // C23での複合リテラルに対するsizeof
    size_t int_array_size = sizeof((int[]){1, 2, 3, 4, 5});
    printf("int配列リテラルのサイズ: %zu バイト\n", int_array_size);
    
    // ビットフィールドを含む構造体のサイズ
    printf("BitField構造体のサイズ: %zu バイト\n", sizeof(struct BitField));
    
    return 0;
}

sizeofとプリプロセッサの比較

[編集]

sizeof演算子はコンパイル時に評価されますが、プリプロセッサマクロとは異なります。プリプロセッサはソースコードの変換のみを行いますが、sizeofはコンパイラによって処理され、型情報を考慮します:

#include <stdio.h>
#define SIZE_INT 4  // プリプロセッサ定数(プラットフォームに依存)
int main() {
    // コンパイル時に評価されるsizeof
    const size_t size_of_int = sizeof(int);
    
    printf("プリプロセッサによるint型のサイズ: %d バイト\n", SIZE_INT);
    printf("sizeofによるint型のサイズ: %zu バイト\n", size_of_int);
    
    // プラットフォームによっては異なる値が表示される可能性がある
    
    return 0;
}

高度な使用例:型の汎用性

[編集]

sizeof演算子を利用して、様々な型に対応する汎用的な関数を実装できます:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 任意の型の配列を作成する汎用関数
void* create_array(size_t element_size, size_t count, void* init_value) {
    // メモリ割り当て
    void* array = malloc(element_size * count);
    if (array == NULL) {
        return NULL;
    }
    
    // 初期値がある場合は初期化
    if (init_value != NULL) {
        unsigned char* dest = array;
        for (size_t i = 0; i < count; i++) {
            memcpy(dest + (i * element_size), init_value, element_size);
        }
    } else {
        // 初期値がない場合はゼロクリア
        memset(array, 0, element_size * count);
    }
    
    return array;
}

int main() {
    // int型の配列を作成
    int init_int = 42;
    int* int_array = create_array(sizeof(int), 5, &init_int);
    
    printf("int配列: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", int_array[i]);
    }
    printf("\n");
    
    // double型の配列を作成
    double init_double = 3.14;
    double* double_array = create_array(sizeof(double), 5, &init_double);
    
    printf("double配列: ");
    for (int i = 0; i < 5; i++) {
        printf("%.2f ", double_array[i]);
    }
    printf("\n");
    
    // 初期値なしのchar配列(ゼロ初期化)
    char* char_array = create_array(sizeof(char), 10, NULL);
    
    printf("char配列: [");
    for (int i = 0; i < 10; i++) {
        printf("%d ", char_array[i]);
    }
    printf("]\n");
    
    free(int_array);
    free(double_array);
    free(char_array);
    
    return 0;
}

まとめ

[編集]

C言語におけるsizeof演算子は、型やオブジェクトのサイズを取得するための強力なツールです。主な特性は以下の通りです:

  1. 大部分の場合、コンパイル時に評価される
  2. 可変長配列に使用される場合は実行時に評価される
  3. オペランドが式の場合でも、その式は評価されない
  4. 結果はsize_t型(符号なし整数型)で返される
  5. メモリ管理、配列操作、ポータブルコードの作成に不可欠

C23では、sizeof演算子の挙動がさらに明確化され、より一貫性のある結果が得られるようになっています。sizeof演算子を適切に使用することで、プラットフォームに依存しない堅牢なコードを書くことができます。