コンテンツにスキップ

C言語/void

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

はじめに

[編集]

C言語において、voidキーワードは特殊な目的を持つ型指定子です。voidは「値がない」あるいは「型がない」ことを表し、複数の文脈で異なる意味を持ちます。C23(ISO/IEC 9899:2024)においてもvoidの基本的な機能は維持されつつ、一部の使用法が明確化されています。この章では、voidの様々な用法と意味について詳しく解説します。

voidの基本概念

[編集]

voidは、C言語において「何もない」という概念を表現するために導入されました。主に以下の3つの文脈で使用されます:

  1. 関数の戻り値型としてのvoid
  2. 関数のパラメータとしてのvoid
  3. 汎用ポインタとしてのvoid*

関数の戻り値型としてのvoid

[編集]

関数の戻り値型としてvoidを指定すると、その関数は値を返さないことを意味します。

#include <stdio.h>
void print_message(const char* message) {
    printf("%s\n", message);
    // return文は不要(値を返さない)
}

int main() {
    print_message("Hello, World!");
    return 0;
}

この例では、print_message関数は戻り値を持たないため、void型として宣言されています。この関数内ではreturn文を省略できます(return;と書くこともできます)。

関数のパラメータとしてのvoid

[編集]

関数が引数を取らない場合、パラメータリストにvoidを指定します。

#include <stdio.h>
#include <time.h>
void print_current_time(void) {
    time_t now = time(NULL);
    printf("現在時刻: %s", ctime(&now));
}

int main() {
    print_current_time();
    return 0;
}

この例では、printcurrenttime関数は引数を取らないため、パラメータリストにvoidが指定されています。C言語では、引数なしの関数宣言としてprintcurrenttime()と記述することもできますが、これはC++との互換性の問題を引き起こす可能性があるため、明示的にvoidを指定することが推奨されています。

空のパラメータリストとvoidパラメータの違い

[編集]
宣言の形式 意味 C23での扱い
void func() K&R形式の宣言。引数の数と型が指定されていない 非推奨、ただし後方互換性のため許可
void func(void) 関数が引数を取らないことを明示 推奨される形式
// K&R形式(非推奨)
int old_style() {
    return 42;
}

// プロトタイプ形式(推奨)
int new_style(void) {
    return 42;
}

汎用ポインタとしてのvoid*

[編集]

void*はC言語における汎用ポインタ型です。任意の型のオブジェクトのアドレスを格納できますが、参照先のオブジェクトの型情報は持ちません。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void* allocate_memory(size_t size) {
    return malloc(size);
}

int main() {
    // 整数配列用のメモリ割り当て
    int* int_array = allocate_memory(5 * sizeof(int));
    for (int i = 0; i < 5; i++) {
        int_array[i] = i * 10;
    }
    
    // 文字列用のメモリ割り当て
    char* str = allocate_memory(20);
    strcpy(str, "Hello, void*!");
    
    printf("整数配列: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", int_array[i]);
    }
    printf("\n文字列: %s\n", str);
    
    free(int_array);
    free(str);
    return 0;
}

この例では、allocate_memory関数はvoid*型を返します。この関数の戻り値は、呼び出し側で適切な型にキャストして使用されています。

void*の特性

[編集]

void*型には以下のような特性があります:

  1. あらゆる型のオブジェクトポインタを格納できる
  2. void*型から特定の型へのキャストが必要(逆は不要)
  3. ポインタ演算は直接適用できない
  4. データの内容にアクセスするには、型キャストが必要
#include <stdio.h>
int main() {
    int num = 42;
    double pi = 3.14159;
    
    // 任意の型のポインタをvoid*に格納
    void* ptr1 = &num;  // 暗黙的な変換が許可される
    void* ptr2 = &pi;
    
    // void*から特定の型への変換は明示的なキャストが必要
    int* int_ptr = (int*)ptr1;
    double* double_ptr = (double*)ptr2;
    
    printf("整数値: %d\n", *int_ptr);
    printf("浮動小数点値: %f\n", *double_ptr);
    
    // 以下はコンパイルエラーになる
    // printf("直接アクセス: %d\n", *ptr1);  // エラー: void*型は間接参照できない
    // ptr1++;  // エラー: void*型には演算子++を適用できない
    
    return 0;
}

C23におけるvoid

[編集]

C23では、voidの使用法に関するいくつかの明確化が行われています。特に、void式の評価に関する規則が明確になり、_Generic選択式におけるvoid型の扱いが改善されています。

#include <stdio.h>
// C23における_Genericでのvoid型の扱い
#define TYPE_NAME(x) _Generic((x), \
    void: "void", \
    int: "int", \
    double: "double", \
    default: "unknown")

void dummy_function(void) {
    // 何もしない
}

int main() {
    // 関数ポインタの型を調べる
    void (*func_ptr)(void) = dummy_function;
    printf("関数ポインタの型: %s\n", TYPE_NAME(func_ptr));
    
    return 0;
}

コールバック関数とvoid*

[編集]

void*の一般的な使用法の一つは、コールバック関数にユーザーデータを渡す際のパラメータとしての利用です。

#include <stdio.h>
#include <stdlib.h>
// コールバック関数の型定義
typedef void (*Callback)(void* user_data);

// コールバック関数を実行する関数
void execute_with_callback(Callback callback, void* user_data) {
    printf("コールバック実行前...\n");
    callback(user_data);
    printf("コールバック実行後...\n");
}

// 整数データ用のコールバック関数
void int_callback(void* data) {
    int* value = (int*)data;
    printf("整数値: %d\n", *value);
}

// 文字列データ用のコールバック関数
void string_callback(void* data) {
    char* str = (char*)data;
    printf("文字列: %s\n", str);
}

int main() {
    int number = 42;
    char message[] = "Hello, Callback!";
    
    execute_with_callback(int_callback, &number);
    execute_with_callback(string_callback, message);
    
    return 0;
}

この例では、void*型を使用して異なる型のデータをコールバック関数に渡しています。

voidの高度な使用例

[編集]

メモリ操作関数

[編集]

標準ライブラリのmemcpymemsetなどのメモリ操作関数は、void*を使用して任意の型のデータを扱います。

#include <stdio.h>
#include <string.h>
// 独自のメモリコピー関数
void* my_memcpy(void* dest, const void* src, size_t n) {
    unsigned char* d = dest;
    const unsigned char* s = src;
    
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }
    
    return dest;
}

int main() {
    int source_array[5] = {10, 20, 30, 40, 50};
    int dest_array[5];
    
    my_memcpy(dest_array, source_array, sizeof(source_array));
    
    printf("コピー後の配列: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest_array[i]);
    }
    printf("\n");
    
    return 0;
}

多態的なデータ構造

[編集]

void*を使用して、異なる型のデータを格納できる多態的なデータ構造を実装できます。

#include <stdio.h>
#include <stdlib.h>
// データの型を識別するための列挙型
typedef enum {
    TYPE_INT,
    TYPE_DOUBLE,
    TYPE_STRING
} DataType;

// 汎用データ構造
typedef struct {
    void* data;
    DataType type;
} GenericData;

// 汎用データを作成する関数
GenericData* create_data(void* data, DataType type) {
    GenericData* gd = malloc(sizeof(GenericData));
    gd->data = data;
    gd->type = type;
    return gd;
}

// 汎用データを表示する関数
void print_data(GenericData* gd) {
    switch (gd->type) {
        case TYPE_INT:
            printf("整数: %d\n", *(int*)gd->data);
            break;
        case TYPE_DOUBLE:
            printf("浮動小数点: %f\n", *(double*)gd->data);
            break;
        case TYPE_STRING:
            printf("文字列: %s\n", (char*)gd->data);
            break;
    }
}

int main() {
    int* num = malloc(sizeof(int));
    *num = 42;
    
    double* real = malloc(sizeof(double));
    *real = 3.14159;
    
    char* str = "Hello, Generic Data!";
    
    GenericData* gd1 = create_data(num, TYPE_INT);
    GenericData* gd2 = create_data(real, TYPE_DOUBLE);
    GenericData* gd3 = create_data(str, TYPE_STRING);
    
    print_data(gd1);
    print_data(gd2);
    print_data(gd3);
    
    free(num);
    free(real);
    free(gd1);
    free(gd2);
    free(gd3);
    
    return 0;
}

まとめ

[編集]

C言語におけるvoidキーワードは、その文脈によって3つの主要な役割を持ちます:

  1. 関数が値を返さないことを示す戻り値型
  2. 関数が引数を取らないことを示すパラメータ
  3. 型に依存しない汎用ポインタとしてのvoid*

C23では、voidの使用法が明確化され、より一貫した動作が保証されています。voidの適切な使用は、C言語で型安全かつ柔軟なプログラムを書くための重要な要素です。特にvoid*型は、低レベルのメモリ操作や多態的なデータ構造の実装など、C言語の強力な機能を支える基盤となっています。