C言語で構造体を初期化する方法

7, 2020-08-07

目次

構造体を初期化する

C言語で構造体を初期化するにはいくつか方法があります。
それは↓のような方法です。

  1. 初期化子リストで0クリアする
struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {0};  // <- これが初期化子リスト
    return 0;
}
  1. 初期化子リストを使う
struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {4, 8.2};  // <- これが初期化子リスト
    return 0;
}
  1. 初期化子リストと指示初期化子を使う(c99)
struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {
        .age = 4,  // <- これが指示初期化子
        .weight = 8.2,  // <- これも指示初期化子
    };
    return 0;
}
  1. メンバに直接代入する
struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;

    cat.age = 4;
    cat.weight = 8.2;

    return 0;
}
  1. memsetで0クリアする
struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;

    memset(&cat, 0, sizeof(struct animal));

    return 0;
}

初期化子リストで0クリアする

C言語の構造体の宣言は↓のように書きます。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;
    return 0;
}

構造体struct animalの構造体変数catを宣言しています。
このままだとcatのメンバは初期化されません。
これを初期化するには初期化子リストを使います。

struct animal cat = {0};

初期化子リストは波括弧({})でくくられたリストのことです。
これの最初の要素に0を指定すると、構造体変数の全体を0クリアすることが可能です。
よく使うので覚えておきましょう。

ちなみに一時オブジェクトを利用して定義済みの構造体変数を初期化する方法も紹介しておきます。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {4, 8.2};

    cat = (struct animal) {0};

    return 0;
}

(struct animal) {0}で構造体変数の一時オブジェクトを0クリアして、それを構造体変数catに代入しています。
catのメンバは0クリアされます。

初期化子リストを使う

初期化子リストの一般的な使い方は、↓のように構造体で宣言したメンバ変数と同じ順番で、値を初期化していく方法です。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {4, 8.2};
    return 0;
}

構造体変数catを初期化子リスト({4, 8.2})で初期化しています。
catのメンバage4で、メンバweight8.2の値で初期化されます。
構造体のメンバの宣言の順番と、初期化子リストの要素の順番は一致させる必要があるので注意が必要です。

コードがわかりづらくなる場合は、C99以降に限りますが指示初期化子の使用を検討してください。

初期化子リストと指示初期化子を使う(c99)

C99以降のC言語では指示初期化子が使えます。
これを使うと、初期化子リストの中で初期化したいメンバを指定することが出来ます。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat = {
        .age = 4,
        .weight = 8.2,
    };
    return 0;
}

↑の場合、age4で、weight8.2で初期化されます。
指示初期化子を使う場合は、初期化子リストの中のメンバの順番は不問です。
つまり、↓のような初期化も可能です。

struct animal cat = {
    .weight = 8.2,
    .age = 4,
};

メンバに直接代入する

これも広義の意味で初期化に含まれるので紹介します。
構造体変数のメンバに直接、値を代入していく初期化です。
厳密には宣言と同時に初期化されるのが「初期化」と呼びますが、処理の文脈によってはこれも初期化と呼ばれます。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;

    cat.age = 4;
    cat.weight = 8.2;

    return 0;
}

memsetで0クリアする

一度定義した構造体変数を何度も0クリアしたい場合は、memsetがよく使われます。

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;

    memset(&cat, 0, sizeof(struct animal));

    return 0;
}

memsetの第1引数には初期化したい変数のアドレスを、第2引数には初期化に使う値を、最後の引数には構造体のバイト数を渡します。

おまけ: memsetと一時オブジェクトの0クリア、どっちが速い?

一時オブジェクトを使った0クリアとmemsetを使った0クリア、どちらが速いのでしょうか?
比較してみたいと思います。

検証用のコードはこちら。

#include <stdio.h>
#include <string.h>
#include <time.h>

struct animal {
    int age;
    double weight;
};

int main(void) {
    struct animal cat;
    clock_t start, end;

    start = clock();
    for (long i = 0; i < 1000000000; i++) {
        cat = (struct animal) {0};
    }
    end = clock();

    printf("一時オブジェクト: %.2f秒かかりました\n", (double)((end - start) / CLOCKS_PER_SEC));

    start = clock();
    for (long i = 0; i < 1000000000; i++) {
        memset(&cat, 0, sizeof(struct animal));
    }
    end = clock();

    printf("memset: %.2f秒かかりました\n", (double)((end - start) / CLOCKS_PER_SEC));
    return 0;
}

↑の結果は↓のようになります。

一時オブジェクト: 1.00秒かかりました
memset: 2.00秒かかりました

なんと一時オブジェクトのほうが速いですね。
これは私には意外な結果です。
予想だと一時オブジェクトの作成分、遅くなってるんじゃないかと思ったのですが。

ちなみに今回のコードはコンパイラの最適化を効かせるとどちらも0秒になります。

すごいぞコンパイラ

まとめ

C言語の構造体の初期化について見てきました。
構造体の初期化方法には↓のような方法があることがわかりました。

  1. 初期化子リストで0クリアする
  2. 初期化子リストを使う
  3. 初期化子リストと指示初期化子を使う(c99)
  4. メンバに直接代入する
  5. memsetで0クリアする

シーンによってどれを使うかは書き手の判断によります。
場合場合によって使えるやつを選んでいきましょう。