先頭へ戻る

2つの構造体が一致しているか調べる | Programming Place Plus C言語編 逆引き

Programming Place Plus トップページ -- C言語編 -- 逆引き

先頭へ戻る

この章の概要

この章の概要です。

目的

定義済みの2つの構造体変数のすべてのメンバの値が一致しているかどうかを判定したいとします。

#include <stdio.h>

struct Data_tag {
    int a;
    double b;
    char c[4];
    int d[3];
};

int main(void)
{
    struct Data_tag s1 = { 5, 1.2, "abc", {0,1,2} };
    struct Data_tag s2 = s1;

    // s1 と s2 は同じ

    s2.a = 100;

    // s1 と s2 は同じではない

    return 0;
}

方法①(メンバを1つ1つ比較する)

各メンバを1つ1つ比較します。単純ですが、正しく動作する方法はこれしかありません。

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

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

struct Data_tag {
    int a;
    double b;
    char c[4];
    int d[3];
};

/*
    struct Data_tag のメンバがすべて一致しているか調べる

    引数
        s1: 構造体を指すポインタ
        s2: 構造体を指すポインタ
    戻り値
        一致していたら true、不一致なら false
*/
bool data_equal(const struct Data_tag* s1, const struct Data_tag* s2)
{
    if (s1->a != s2->a) { return false; }
    if (s1->b != s2->b) { return false; }
    if (strcmp(s1->c, s2->c) != 0) { return false; }
    for (size_t i = 0; i < SIZE_OF_ARRAY(s1->d); ++i) {
        if (s1->d[i] != s2->d[i]) { return false; }
    }

    return true;
}

int main(void)
{
    struct Data_tag s1 = { 5, 1.2, "abc", {0,1,2} };
    struct Data_tag s2 = s1;

    printf("%d\n", data_equal(&s1, &s2));

    s2.a = 100;

    printf("%d\n", data_equal(&s1, &s2));

    return 0;
}

実行結果:

1
0

発想としては単純なものの、1つ1つ比較するということはとても面倒です。また、後から構造体型の定義のほうで、メンバを増やしたり減らしたり、型や名前を変えたりといった変更が行われる可能性もあり、そのときに、比較のコードも修正する手間があります。

特に、メンバの追加や、型の変更は、既存の比較コードがそのままコンパイルできてしまう可能性があるため、注意が必要です。

このような面倒臭さと危険性があるので、せめて関数にして、構造体型の定義の近くに置いておくことを勧めます。今回は、data_equal関数を作成しました。

比較関数の引数は、構造体を指すポインタにしましょう。構造体の大きさ次第ですが、そのまま渡すのは処理効率が悪いです(第33章)。また、構造体メンバを書き換えないので、const修飾子(第32章)も付けるべきです。

data_equal関数の実装をみると分かるように、== や != の演算子で比較するだけでは済まない場合があります。

メンバが文字列の場合は、strcmp関数を使った比較になります。

ここでは、char[4] なので 4要素分すべてを丁寧に比較したほうがいいという考え方もありますが、'\0' が末尾にあっての文字列なので、'\0' の後ろまで比較する必要はないでしょう。

メンバが文字列以外の配列の場合は、memcmp関数を使えるかも知れませんし、for文で1つ1つ調べる必要があるかも知れません。構造体型の配列の場合には、memcmp関数は使えません。詳しいことは、逆引き「2つの配列が一致しているか調べる」で取り上げています。

逆引き「2つの配列が一致しているか調べる」では、配列を比較するときには、要素数が同じであることも調べなければならないことを説明しています。今回は、比較する2つの配列はいずれも、struct Data_tag に含まれる int[3] であることが分かっていますから、要素数は必ず一致するので、要素数の確認は省いています。

メンバが構造体の場合は、その構造体用の比較関数を呼ぶようにすればいいです。

メンバがポインタの場合には、方針を考えなければなりません。たいていは、単にポインタ同士を比較すればいいですが、ポインタが指し示している先のものを比較するほうが適切な場合があるかもしれません。

方法②(memcmp関数を使う)

値を比較するという話になると、memcmp関数を使いたくなるかも知れませんが、構造体に対して使うのは誤りです

構造体のメンバとメンバの間や、最後のメンバの後ろには、パディングという余分な空き領域が入ることがあるためです(第26章)。パディングの部分に何があるかは不定ですから、ここを巻き込んだ比較を行うと、どんな結果になるか予測できません。


参考リンク


更新履歴

'2019/9/23 新規作成。



逆引きのトップページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー