std::memcmp vs. std::strcmp/std::string:C++での文字列比較を徹底比較

2025-05-31

std::memcmp とは

std::memcmp は、指定されたバイト数だけ、2つのメモリブロックをバイナリ的に(バイトごとに)比較する関数です。これは、特定のデータ型に依存せず、生のメモリ内容を比較する際に非常に便利です。

ヘッダーファイル

std::memcmp を使用するには、以下のヘッダーファイルをインクルードする必要があります。

#include <cstring>

または、C++ではC互換のヘッダーとして <string.h> を使用することもできますが、推奨されるのは <cstring> です。

関数のプロトタイプ

std::memcmp のプロトタイプは以下のようになっています。

int std::memcmp(const void* ptr1, const void* ptr2, std::size_t num);

引数

  • num: 比較するバイト数です。std::size_t 型は通常、符号なし整数型です。
  • ptr2: 比較する2番目のメモリブロックへのポインタです。const void* 型なので、任意の型のポインタを渡すことができますが、関数内で内容は変更されません。
  • ptr1: 比較する最初のメモリブロックへのポインタです。const void* 型なので、任意の型のポインタを渡すことができますが、関数内で内容は変更されません。

戻り値

std::memcmp は、比較結果を示す整数値を返します。

  • 正の値: ptr1 が指すメモリブロックの内容が、最初に異なるバイトで ptr2 が指すメモリブロックの内容よりも大きい場合。
  • 負の値: ptr1 が指すメモリブロックの内容が、最初に異なるバイトで ptr2 が指すメモリブロックの内容よりも小さい場合。
  • 0 (ゼロ): ptr1ptr2 が指すメモリブロックの内容が、指定された num バイトの間、完全に同じである場合。

この「小さい」「大きい」は、バイトの数値(通常はASCII値や生のバイナリ値)に基づいて比較されます。

動作原理

std::memcmp は、ptr1 が指すメモリの先頭から num バイト、ptr2 が指すメモリの先頭から num バイトを順に1バイトずつ比較していきます。最初に異なるバイトが見つかった場合、その時点でのバイト値の比較結果を返します。num バイトすべてが同じであれば 0 を返します。

使用例

#include <iostream>
#include <cstring> // std::memcmp のために必要

int main() {
    char arr1[] = "Hello";
    char arr2[] = "Hello";
    char arr3[] = "World";
    char arr4[] = "HellO"; // 大文字小文字が異なる

    // 例1: 同じ文字列の比較
    int result1 = std::memcmp(arr1, arr2, strlen(arr1));
    if (result1 == 0) {
        std::cout << "arr1 と arr2 は同じです。" << std::endl; // 出力される
    } else {
        std::cout << "arr1 と arr2 は異なります。" << std::endl;
    }

    // 例2: 異なる文字列の比較
    int result2 = std::memcmp(arr1, arr3, strlen(arr1)); // 実際にはstrlen(arr1)より長い配列なので注意が必要だが、ここでは簡略化
    if (result2 == 0) {
        std::cout << "arr1 と arr3 は同じです。" << std::endl;
    } else if (result2 < 0) {
        std::cout << "arr1 は arr3 より小さいです。" << std::cout << " (結果: " << result2 << ")" << std::endl; // 出力される (H vs W で arr1 が小さい)
    } else {
        std::cout << "arr1 は arr3 より大きいです。" << std::endl;
    }

    // 例3: 大文字小文字の違い
    int result3 = std::memcmp(arr1, arr4, strlen(arr1));
    if (result3 == 0) {
        std::cout << "arr1 と arr4 は同じです。" << std::endl;
    } else if (result3 < 0) {
        std::cout << "arr1 は arr4 より小さいです。" << std::cout << " (結果: " << result3 << ")" << std::endl; // 出力される (o vs O で arr1 が大きい)
    } else {
        std::cout << "arr1 は arr4 より大きいです。" << std::cout << " (結果: " << result3 << ")" << std::endl;
    }

    // 例4: 構造体の比較 (危険な場合がある)
    struct MyStruct {
        int id;
        double value;
    };

    MyStruct s1 = {10, 20.5};
    MyStruct s2 = {10, 20.5};
    MyStruct s3 = {11, 20.5};

    // 構造体の生のメモリを比較
    int result4 = std::memcmp(&s1, &s2, sizeof(MyStruct));
    if (result4 == 0) {
        std::cout << "s1 と s2 のメモリ内容は同じです。" << std::endl; // 出力される
    } else {
        std::cout << "s1 と s2 のメモリ内容は異なります。" << std::endl;
    }

    int result5 = std::memcmp(&s1, &s3, sizeof(MyStruct));
    if (result5 == 0) {
        std::cout << "s1 と s3 のメモリ内容は同じです。" << std::endl;
    } else {
        std::cout << "s1 と s3 のメモリ内容は異なります。" << std::endl; // 出力される
    }

    // パディングによる注意点
    struct MyPaddedStruct {
        char a;
        int b;
        char c;
    };
    MyPaddedStruct ps1 = {'A', 1, 'X'};
    MyPaddedStruct ps2 = {'A', 1, 'X'};

    // 構造体の比較は、コンパイラによるパディングによって予期しない結果になることがある
    // ps1 と ps2 が論理的に同じでも、パディングバイトが異なる値を持つと memcmp は異なる結果を返す
    int result6 = std::memcmp(&ps1, &ps2, sizeof(MyPaddedStruct));
    if (result6 == 0) {
        std::cout << "ps1 と ps2 のメモリ内容は同じです。" << std::endl;
    } else {
        std::cout << "ps1 と ps2 のメモリ内容は異なります。" << std::endl; // パディングによっては異なることがある
    }


    return 0;
}
  1. 生のメモリ比較: std::memcmp は型を意識せず、単にバイト列を比較します。これは、特定のデータ型やオブジェクトのセマンティックな比較には適さない場合があります。
  2. Null終端文字列: std::memcmp はNull終端を認識しません。文字列を比較する場合は、std::strcmpstd::strncmp の方が適切です。std::memcmp で文字列を比較する場合、比較するバイト数 (num) は文字列の長さ(strlenの結果など)で明示的に指定する必要があります。
  3. パディング: 構造体やクラスのインスタンスを std::memcmp で比較する場合、コンパイラによって挿入されるパディングバイトによって問題が生じることがあります。たとえ論理的に同じ内容を持つ2つの構造体であっても、パディングバイトの値が異なると std::memcmp は異なるものと判断してしまいます。構造体の比較には、各メンバーを個別に比較するか、operator== をオーバーロードするなどの方法が推奨されます。
  4. ポインタの指す有効な領域: ptr1ptr2 が指すメモリ領域は、少なくとも num バイトの有効なメモリを含んでいる必要があります。そうでなければ、未定義動作(セグメンテーション違反など)が発生する可能性があります。
  5. バイトオーダー(エンディアン): 複数のバイトで構成される数値型(int, long, float, double など)を std::memcmp で比較する場合、システムのバイトオーダー(ビッグエンディアンかリトルエンディアンか)に依存します。異なるバイトオーダーのシステム間で比較すると、予期しない結果になる可能性があります。


比較するバイト数の誤り (num 引数の間違い)

エラーの原因
std::memcmp(ptr1, ptr2, num)num 引数に、比較したい実際のバイト数よりも小さい値や大きい値を指定してしまうケースです。

  • 大きすぎる場合
    比較しようとしているメモリ領域の範囲を超えて読み込みが発生し、未定義動作 (Undefined Behavior) を引き起こす可能性があります。これは、セグメンテーション違反やクラッシュにつながることがよくあります。特に、配列の終端を超えて読み込んでしまう場合に発生します。
    char s1[] = "Hello"; // サイズは6バイト (Null終端含む)
    char s2[] = "Hello";
    // sizeof(s1) は6だが、誤って10バイト比較しようとする
    // s1やs2のメモリ領域の後に何があるか不明なため、未定義動作
    if (std::memcmp(s1, s2, 10) == 0) {
        // 動作は保証されない
    }
    
    このエラーはデバッグが難しいことがあります。なぜなら、未定義動作は必ずしもクラッシュするとは限らず、環境によって異なる振る舞いをすることがあるからです。
  • 小さすぎる場合
    データの一部しか比較されず、論理的には異なるメモリ領域が std::memcmp では同じと判断される可能性があります。
    char s1[] = "HelloWorld";
    char s2[] = "HelloMars!";
    // 最初の5バイトは同じなので、strlen("Hello") = 5 で比較すると同じと判定される
    if (std::memcmp(s1, s2, 5) == 0) {
        // ここに入ってしまう
        std::cout << "同じと判定されましたが、実際は異なります。" << std::endl;
    }
    

トラブルシューティング

  • デバッグツールを使用する
    Valgrindのようなメモリデバッグツールは、ヒープオーバーフローやアンダーフロー、無効なメモリアクセスを検出するのに非常に役立ちます。
  • 配列の範囲を意識する
    ポインタが指すメモリ領域が、指定された num バイトを安全に読み込めることを常に確認します。
  • num 引数を正確に指定する
    比較したいデータ型のサイズ (sizeof(type)) や、文字列の長さ (strlen) などを正確に計算して渡します。

Null終端文字列の比較 (std::strcmp との混同)

エラーの原因
std::memcmp はNull終端を特別な意味として扱いません。そのため、Null終端文字列を比較する際に std::strcmpstd::strncmp の代わりに std::memcmp を使ってしまうと、意図しない結果になることがあります。

char s1[] = "apple";
char s2[] = "apple\0xyz"; // Null終端は同じ位置にあるが、その後に異なるデータがある

// strcmpでは同じと判定される
if (std::strcmp(s1, s2) == 0) {
    std::cout << "strcmp: 同じです" << std::endl; // 出力される
}

// memcmpでは異なるバイトまで比較されてしまい、異なる判定になる
// strlen(s1) は 5
if (std::memcmp(s1, s2, strlen(s1) + 4) == 0) { // s1 + '\0' + 'x' + 'y' + 'z' の4バイト
    std::cout << "memcmp: 同じです" << std::endl;
} else {
    std::cout << "memcmp: 異なります" << std::endl; // 出力される
}

トラブルシューティング

  • std::memcmp を使う場合は strlen を活用する
    どうしても std::memcmp を使いたい場合は、比較するバイト数を strlen(str) + 1 (Null終端を含める場合) のように明示的に指定します。
  • 文字列の比較には std::strcmp または std::strncmp を使用する
    Null終端文字列のセマンティックな比較には、これらの関数が適切です。

構造体やクラスの比較 (パディングと論理的同等性)

エラーの原因
C++の構造体やクラスは、コンパイラによってパフォーマンス最適化のためにパディングバイトが挿入されることがあります。このパディングバイトは初期化されない場合があり、同じ論理的な値を持つ2つのオブジェクトでも、パディングバイトの値が異なるために std::memcmp で「異なる」と判定されることがあります。また、std::stringstd::vector のような非トリビアル型(Non-Trivial Types)のオブジェクトを std::memcmp で比較しても、オブジェクトの中身ではなく、それらが内部的に持っているポインタなどの表現(オブジェクト表現)を比較するだけであり、期待する「中身の比較」にはなりません。

struct MyStruct {
    char c;
    int i;
};

int main() {
    MyStruct s1 = {'A', 10};
    MyStruct s2 = {'A', 10};
    // s1とs2は論理的には同じだが、パディングバイトの値が異なる可能性がある
    if (std::memcmp(&s1, &s2, sizeof(MyStruct)) == 0) {
        std::cout << "同じと判定されました。" << std::endl;
    } else {
        std::cout << "異なると判定されました。" << std::endl; // パディングによってはここに入る
    }

    std::string str1 = "hello";
    std::string str2 = "hello";
    // std::string オブジェクトの内部表現を比較するため、中身の比較にはならない
    if (std::memcmp(&str1, &str2, sizeof(std::string)) == 0) {
        std::cout << "stringのメモリ内容は同じです。" << std::endl;
    } else {
        std::cout << "stringのメモリ内容は異なります。" << std::endl; // 異なる可能性がある
    }
}

トラブルシューティング

  • パディングを回避する
    構造体のメンバーの配置順序を工夫したり、#pragma pack などのコンパイラ拡張を使用してパディングを制御することは可能ですが、ポータビリティが低下する可能性があります。
  • トリビアル型に限定する
    std::memcmp は、トリビアルにコピー可能な型(int, float, 生の配列など、コンストラクタやデストラクタを持たない単純なデータ型)の比較にのみ安全に使用できます。
  • operator== をオーバーロードする
    クラスや構造体の論理的な同等性を定義するために、operator== を適切にオーバーロードします。
  • 各メンバーを個別に比較する
    構造体の場合は、メンバーごとに比較演算子 (==) を使って比較します。

バイトオーダー (エンディアン) の違い

エラーの原因
int, float, double のような複数バイトで構成される数値型は、メモリ上でのバイトの並び順(バイトオーダー、エンディアン)がシステムによって異なる場合があります(ビッグエンディアン vs リトルエンディアン)。異なるバイトオーダーを持つシステム間で std::memcmp を使ってこれらの数値を比較すると、意図しない結果になります。

// リトルエンディアンのシステムでは 0x01 0x00 0x00 0x00 となる
// ビッグエンディアンのシステムでは 0x00 0x00 0x00 0x01 となる
int a = 1;
int b = 1;

// 同じシステム内では通常問題ないが、異なるシステム間で比較すると問題になる
if (std::memcmp(&a, &b, sizeof(int)) == 0) {
    std::cout << "同じです。" << std::endl;
}

トラブルシューティング

  • ネットワークバイトオーダーへの変換
    ネットワーク通信などで異なるシステム間で数値をやり取りする場合は、htonl, ntohl などの関数を使ってネットワークバイトオーダー(ビッグエンディアン)に変換してから比較・送信します。
  • 数値の比較には比較演算子 (<, >, ==) を使用する
    数値のセマンティックな比較には、std::memcmp ではなく、適切な比較演算子を使用します。

ポインタが無効なメモリを指している場合

エラーの原因
ptr1 または ptr2 がヌルポインタであったり、解放済みのメモリ、スタック上の無効な領域などを指している状態で std::memcmp を呼び出すと、未定義動作が発生します。これは、最も深刻でデバッグが難しいエラーの一つです。

int* p = nullptr;
int data = 10;
// pがヌルポインタの状態でアクセスしようとするため未定義動作
// コンパイラによっては警告すら出ない場合がある
if (std::memcmp(p, &data, sizeof(int)) == 0) {
    // 未定義動作
}
  • スマートポインタを使用する
    std::unique_ptrstd::shared_ptr を使うことで、メモリの解放忘れや二重解放といった問題を防ぎ、ポインタの安全性を高めることができます。
  • メモリデバッグツールを活用する
    Valgrind、AddressSanitizer (ASan) などのツールは、無効なメモリアクセスを強力に検出してくれます。
  • ポインタの妥当性を常に確認する
    関数を呼び出す前に、ポインタが有効なメモリを指していることを確認します。


std::memcmp<cstring> ヘッダーに含まれており、2つのメモリ領域をバイト単位で比較します。

例1: 2つの配列の比較

最も基本的な使用例です。同じ型の配列の内容を比較します。

#include <iostream>
#include <cstring> // std::memcmp のために必要
#include <vector>  // std::vector のために必要

int main() {
    // 例1.1: char 配列 (文字列) の比較
    char arr1[] = "Hello";
    char arr2[] = "Hello";
    char arr3[] = "World";
    char arr4[] = "hellO"; // 大文字小文字が異なる

    // arr1 と arr2 の比較: 同じ (5バイト比較)
    // strlen(arr1) は Null 終端を除くバイト数を返す
    int result1 = std::memcmp(arr1, arr2, strlen(arr1));
    if (result1 == 0) {
        std::cout << "例1.1: arr1 と arr2 は同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例1.1: arr1 と arr2 は異なります。" << std::endl;
    }

    // arr1 と arr3 の比較: 異なる (最初の 'H' と 'W' で差が出る)
    int result2 = std::memcmp(arr1, arr3, std::min(strlen(arr1), strlen(arr3))); // より短い方の長さに合わせる
    if (result2 == 0) {
        std::cout << "例1.1: arr1 と arr3 は同じです。" << std::endl;
    } else if (result2 < 0) {
        std::cout << "例1.1: arr1 は arr3 より小さいです。" << std::endl; // 出力: 小さいです。
    } else {
        std::cout << "例1.1: arr1 は arr3 より大きいです。" << std::endl;
    }

    // arr1 と arr4 の比較: 異なる ('e' と 'E'、または 'o' と 'O' で差が出る)
    // ASCII コードで 'o' (111) は 'O' (79) より大きい
    int result3 = std::memcmp(arr1, arr4, strlen(arr1));
    if (result3 == 0) {
        std::cout << "例1.1: arr1 と arr4 は同じです。" << std::endl;
    } else if (result3 < 0) {
        std::cout << "例1.1: arr1 は arr4 より小さいです。" << std::endl;
    } else {
        std::cout << "例1.1: arr1 は arr4 より大きいです。" << std::endl; // 出力: 大きいです。
    }

    std::cout << std::endl;

    // 例1.2: int 配列の比較
    int i_arr1[] = {1, 2, 3, 4, 5};
    int i_arr2[] = {1, 2, 3, 4, 5};
    int i_arr3[] = {1, 2, 9, 4, 5}; // 3番目の要素が異なる

    // sizeof(i_arr1) は配列全体のバイト数 (intのサイズ * 要素数)
    int result4 = std::memcmp(i_arr1, i_arr2, sizeof(i_arr1));
    if (result4 == 0) {
        std::cout << "例1.2: i_arr1 と i_arr2 は同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例1.2: i_arr1 と i_arr2 は異なります。" << std::endl;
    }

    int result5 = std::memcmp(i_arr1, i_arr3, sizeof(i_arr1));
    if (result5 == 0) {
        std::cout << "例1.2: i_arr1 と i_arr3 は同じです。" << std::endl;
    } else if (result5 < 0) {
        std::cout << "例1.2: i_arr1 は i_arr3 より小さいです。" << std::endl; // 出力: 小さいです。
    } else {
        std::cout << "例1.2: i_arr1 は i_arr3 より大きいです。" << std::endl;
    }

    std::cout << std::endl;

    // 例1.3: std::vector<char> の比較
    std::vector<char> v1 = {'a', 'b', 'c'};
    std::vector<char> v2 = {'a', 'b', 'c'};
    std::vector<char> v3 = {'a', 'x', 'c'};

    // vector の raw data へのポインタを取得し、サイズも正確に指定する
    int result6 = std::memcmp(v1.data(), v2.data(), v1.size());
    if (result6 == 0) {
        std::cout << "例1.3: v1 と v2 は同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例1.3: v1 と v2 は異なります。" << std::endl;
    }

    int result7 = std::memcmp(v1.data(), v3.data(), v1.size());
    if (result7 == 0) {
        std::cout << "例1.3: v1 と v3 は同じです。" << std::endl;
    } else if (result7 < 0) {
        std::cout << "例1.3: v1 は v3 より小さいです。" << std::endl; // 出力: 小さいです。
    } else {
        std::cout << "例1.3: v1 は v3 より大きいです。" << std::endl;
    }

    return 0;
}

解説

  • std::vector の場合、data() メンバー関数で内部の生データへのポインタを取得し、size() メンバー関数で要素数を取得します。ただし、size() は要素数なので、比較するバイト数には size() * sizeof(要素の型) を渡す必要があります。しかし、std::vector<char> の場合は size() がそのままバイト数になります。
  • int 配列のような固定サイズのデータの場合、sizeof(配列名) で配列全体のバイト数を取得できます。
  • char 配列(Cスタイルの文字列)の場合、strlen() で Null 終端を除くバイト数を取得し、それを num 引数に渡すのが一般的です。std::memcmp は Null 終端を特別な意味として扱わないため、文字列の比較には std::strcmpstd::strncmp の方が適切であることが多いです。

例2: 構造体の比較 (注意が必要なケース)

std::memcmp を構造体の比較に使うのは、パディングの問題があるため、通常は推奨されません。ただし、特定の条件(パディングがない、またはパディングバイトが常に同じ値に初期化されることが保証される場合など)では使用可能です。

#include <iostream>
#include <cstring> // std::memcmp のために必要

// 例2.1: シンプルな構造体
struct Point {
    int x;
    int y;
};

// 例2.2: パディングが発生する可能性のある構造体
// 一般的なシステムでは、int が4バイトアラインメントされるため、char の後にパディングが入る
struct DataPacket {
    char type; // 1バイト
    int id;    // 4バイト
    char status; // 1バイト
};

int main() {
    // 例2.1: シンプルな構造体の比較
    Point p1 = {10, 20};
    Point p2 = {10, 20};
    Point p3 = {15, 20};

    // sizeof(Point) で構造体全体のバイト数を比較
    int result1 = std::memcmp(&p1, &p2, sizeof(Point));
    if (result1 == 0) {
        std::cout << "例2.1: p1 と p2 のメモリ内容は同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例2.1: p1 と p2 のメモリ内容は異なります。" << std::endl;
    }

    int result2 = std::memcmp(&p1, &p3, sizeof(Point));
    if (result2 == 0) {
        std::cout << "例2.1: p1 と p3 のメモリ内容は同じです。" << std::endl;
    } else {
        std::cout << "例2.1: p1 と p3 のメモリ内容は異なります。" << std::endl; // 出力: 異なります。
    }

    std::cout << std::endl;

    // 例2.2: パディングのある可能性のある構造体の比較
    DataPacket dp1 = {'A', 100, 'S'};
    DataPacket dp2 = {'A', 100, 'S'};

    // dp1 と dp2 は論理的には同じだが、パディングバイトが初期化されていない場合、
    // または異なる値になっている場合、memcmpは「異なる」と判断する可能性がある
    int result3 = std::memcmp(&dp1, &dp2, sizeof(DataPacket));
    if (result3 == 0) {
        std::cout << "例2.2: dp1 と dp2 のメモリ内容は同じです。" << std::endl;
    } else {
        // パディングバイトの値によってはここに入る可能性もある
        std::cout << "例2.2: dp1 と dp2 のメモリ内容は異なります (パディングの可能性)。" << std::endl;
    }

    // 正しい構造体の比較方法 (各メンバーを比較)
    if (dp1.type == dp2.type && dp1.id == dp2.id && dp1.status == dp2.status) {
        std::cout << "例2.2: 論理的に dp1 と dp2 は同じです (各メンバー比較)。" << std::endl; // 推奨される方法
    }

    return 0;
}

解説

  • 推奨される解決策
    構造体の比較には std::memcmp ではなく、各メンバーを個別に比較するロジックを記述するか、operator== をオーバーロードして構造体にとっての「等価性」を定義すべきです。
  • そのため、論理的に同じ内容を持つ2つの DataPacket インスタンスでも、std::memcmp が「異なる」と判定してしまうことがあります。
  • しかし、DataPacket のように異なるサイズ(charint)のメンバーが混在する場合、コンパイラはメモリの効率的なアクセスをのためにメンバー間に「パディング」と呼ばれる空きバイトを挿入することがあります。このパディングバイトは自動的に初期化されないことが多く、異なるインスタンス間で異なるランダムな値を持つ可能性があります。
  • Point のような、メンバーがすべて同じアラインメント要件を持つ単純な構造体であれば、std::memcmp で比較しても問題ないことが多いです。

例3: 生のメモリ領域の比較 (ファイル読み込みなど)

ファイルから読み込んだバイト列や、ネットワークから受信したデータなど、特定のフォーマットを持たない生のバイトデータを比較する際に std::memcmp は有効です。

#include <iostream>
#include <cstring> // std::memcmp のために必要
#include <vector>

int main() {
    // 擬似的なファイルからの読み込みデータ
    std::vector<unsigned char> file_data1 = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04};
    std::vector<unsigned char> file_data2 = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04};
    std::vector<unsigned char> file_data3 = {0xDE, 0xAD, 0xBE, 0xEF, 0x05, 0x06, 0x07, 0x08};

    // データのヘッダー部分 (最初の4バイト) を比較
    const int header_size = 4;

    int result1 = std::memcmp(file_data1.data(), file_data2.data(), header_size);
    if (result1 == 0) {
        std::cout << "例3: file_data1 と file_data2 のヘッダーは同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例3: file_data1 と file_data2 のヘッダーは異なります。" << std::endl;
    }

    int result2 = std::memcmp(file_data1.data(), file_data3.data(), header_size);
    if (result2 == 0) {
        std::cout << "例3: file_data1 と file_data3 のヘッダーは同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例3: file_data1 と file_data3 のヘッダーは異なります。" << std::endl;
    }

    // 全体を比較
    int result3 = std::memcmp(file_data1.data(), file_data3.data(), file_data1.size());
    if (result3 == 0) {
        std::cout << "例3: file_data1 と file_data3 の全体は同じです。" << std::endl;
    } else {
        std::cout << "例3: file_data1 と file_data3 の全体は異なります。" << std::endl; // 出力: 異なります。
    }

    return 0;
}

解説

  • このような生のバイナリデータでは、パディングやバイトオーダーの問題が比較的少なく、std::memcmp が有効に機能します。ただし、データの中に複数のバイトで構成される数値(例: int 型の ID など)が含まれる場合は、バイトオーダーの問題を考慮する必要があります。
  • data() メソッドでベクターの内部バッファへのポインタを取得し、size() で全体のバイト数を指定して比較しています。
  • この例では、std::vector<unsigned char> を使用してバイナリデータを表現しています。unsigned char は1バイトのデータ型として最も適切です。

例4: 部分的な比較

メモリブロック全体ではなく、その一部だけを比較する例です。

#include <iostream>
#include <cstring> // std::memcmp のために必要

int main() {
    char buffer1[20] = "abcdefghijklmnopqrs";
    char buffer2[20] = "abxdefghijklmnopqrs"; // 3番目の文字が異なる
    char buffer3[20] = "abcdefghijkABCDmnopqrs"; // 11番目から異なる

    // 最初の2文字だけ比較 (同じ)
    int result1 = std::memcmp(buffer1, buffer2, 2);
    if (result1 == 0) {
        std::cout << "例4: 最初の2文字は同じです。" << std::endl; // 出力: 同じです。
    } else {
        std::cout << "例4: 最初の2文字は異なります。" << std::endl;
    }

    // 最初の3文字比較 (異なる)
    int result2 = std::memcmp(buffer1, buffer2, 3);
    if (result2 == 0) {
        std::cout << "例4: 最初の3文字は同じです。" << std::endl;
    } else {
        std::cout << "例4: 最初の3文字は異なります。" << std::endl; // 出力: 異なります。
    }

    // buffer1 と buffer3 をオフセットを付けて比較
    // buffer1 の 10バイト目 (インデックス9) から 4バイト比較
    // buffer3 の 10バイト目 (インデックス9) から 4バイト比較
    // buffer1[9]='j', buffer1[10]='k', buffer1[11]='l', buffer1[12]='m'
    // buffer3[9]='j', buffer3[10]='k', buffer3[11]='A', buffer3[12]='B'
    // 'l' (108) vs 'A' (65) で buffer1 の方が大きい
    int result3 = std::memcmp(buffer1 + 10, buffer3 + 10, 4);
    if (result3 == 0) {
        std::cout << "例4: オフセット10からの4バイトは同じです。" << std::endl;
    } else if (result3 < 0) {
        std::cout << "例4: オフセット10からの4バイトで buffer1 は buffer3 より小さいです。" << std::endl;
    } else {
        std::cout << "例4: オフセット10からの4バイトで buffer1 は buffer3 より大きいです。" << std::endl; // 出力: 大きいです。
    }

    return 0;
}

解説

  • これにより、大きなメモリブロックの特定の部分だけを効率的に比較することが可能になります。
  • ptr1ptr2 には、配列名だけでなく、ポインタ演算を使って特定のオフセットから始まるメモリ領域を指すように指定できます(例: buffer1 + 10)。

std::memcmp は、C++において生のメモリをバイト単位で比較するための強力なツールです。特に、バイナリデータ、固定サイズの配列、または型情報が重要ではない状況で役立ちます。

しかし、その低レベルな性質ゆえに、以下のような点に注意が必要です。

  • バイトオーダー
    複数バイトの数値型を比較する際は、異なるシステム間でのバイトオーダーの違いを考慮する必要があります。
  • 未定義動作
    比較範囲を超えてアクセスしたり、無効なポインタを渡すとクラッシュの原因になります。
  • 構造体のパディング
    構造体の比較には、各メンバーの個別比較や operator== オーバーロードの方が安全です。
  • Null終端
    文字列の比較には std::strcmp / std::strncmp の方が適切です。


文字列の比較

std::memcmp は Null 終端を意識しないため、Cスタイルの文字列や std::string の比較には不向きな場合があります。

代替手段: std::strcmp, std::strncmp, std::string::operator==, std::string::compare()

Cスタイルの文字列 (char*) の比較

  • std::strncmp(const char* s1, const char* s2, std::size_t n):

    • Null終端文字列を最大 n バイトまで辞書順に比較します。
    • std::memcmp と似ていますが、Null終端に到達した場合はそこで比較を終了します。
    • ヘッダー: <cstring>
    #include <iostream>
    #include <cstring> // strncmp のため
    
    int main() {
        const char* s1 = "long_string";
        const char* s2 = "long_data";
    
        // 最初の4文字 'long' は同じなので、4バイトまで比較すると同じと判定される
        if (std::strncmp(s1, s2, 4) == 0) {
            std::cout << "s1 と s2 の最初の4文字は同じです (strncmp)" << std::endl; // 出力される
        }
        return 0;
    }
    
  • std::strcmp(const char* s1, const char* s2):

    • 2つのNull終端文字列を辞書順に比較します。
    • 文字列全体を比較し、Null終端まで到達するか、異なる文字が見つかるまで比較を続けます。
    • 戻り値は std::memcmp と同じく、0 (同じ)、負の値 (s1 < s2)、正の値 (s1 > s2) です。
    • ヘッダー: <cstring>
    #include <iostream>
    #include <cstring> // strcmp のため
    
    int main() {
        const char* s1 = "apple";
        const char* s2 = "apple";
        const char* s3 = "apricot";
    
        if (std::strcmp(s1, s2) == 0) {
            std::cout << "s1 と s2 は同じです (strcmp)" << std::endl;
        }
    
        if (std::strcmp(s1, s3) < 0) {
            std::cout << "s1 は s3 より小さいです (strcmp)" << std::endl; // 出力される
        }
        return 0;
    }
    

C++の文字列 (std::string) の比較

std::string は文字列オブジェクトとして設計されており、std::memcmp のような生データ比較はほとんどの場合不適切です。

  • std::string::compare():

    • std::strcmp と同様に、辞書順に比較し、結果を整数で返します。
    • 部分文字列の比較も可能です。
    • ヘッダー: <string>
    #include <iostream>
    #include <string>
    
    int main() {
        std::string s1 = "orange";
        std::string s2 = "apple";
    
        if (s1.compare(s2) > 0) {
            std::cout << "s1 は s2 より大きいです (std::string::compare())" << std::endl; // 出力される
        }
    
        std::string long_str = "This is a test string.";
        std::string sub_str = "test";
        // long_str のインデックス10から4文字と sub_str を比較
        if (long_str.compare(10, 4, sub_str) == 0) {
            std::cout << "部分文字列 'test' が見つかりました。" << std::endl; // 出力される
        }
        return 0;
    }
    
  • std::string::operator==():

    • 最も一般的で推奨される std::string の等値比較方法です。
    • 内容が完全に同じ場合に true を返します。
    • 辞書順での比較には operator<, operator>, operator<=, operator>= なども利用できます。
    • ヘッダー: <string>
    #include <iostream>
    #include <string>
    
    int main() {
        std::string s1 = "Hello";
        std::string s2 = "Hello";
        std::string s3 = "World";
    
        if (s1 == s2) {
            std::cout << "s1 と s2 は同じです (std::string::operator==)" << std::endl; // 出力される
        }
    
        if (s1 != s3) {
            std::cout << "s1 と s3 は異なります (std::string::operator!=)" << std::endl; // 出力される
        }
        return 0;
    }
    

構造体・クラスの比較

std::memcmp は構造体のパディングバイトの問題があるため、C++では推奨されません。

代替手段: メンバーごとの比較 (operator== のオーバーロード), C++20 の三方比較演算子 (<=>)

メンバーごとの比較と operator== のオーバーロード

  • 各メンバーを個別に比較することで、パディングバイトの影響を回避し、意図した比較ロジックを実装できます。

    #include <iostream>
    
    struct Point {
        int x;
        int y;
    
        // operator== のオーバーロード
        bool operator==(const Point& other) const {
            return x == other.x && y == other.y;
        }
    
        // operator!= は operator== を使って実装できる (C++20以前)
        bool operator!=(const Point& other) const {
            return !(*this == other);
        }
    };
    
    // パディングがある可能性のある構造体
    struct Data {
        char c;
        int i;
        // パディングのせいで memcmp は使えない
        // operator== を適切に実装する
        bool operator==(const Data& other) const {
            return c == other.c && i == other.i;
        }
    
        bool operator!=(const Data& other) const {
            return !(*this == other);
        }
    };
    
    int main() {
        Point p1 = {1, 2};
        Point p2 = {1, 2};
        Point p3 = {3, 4};
    
        if (p1 == p2) {
            std::cout << "p1 と p2 は同じです (operator==)" << std::endl; // 出力される
        }
    
        if (p1 != p3) {
            std::cout << "p1 と p3 は異なります (operator!=)" << std::endl; // 出力される
        }
    
        Data d1 = {'A', 10};
        Data d2 = {'A', 10};
        Data d3 = {'B', 10};
    
        if (d1 == d2) {
            std::cout << "d1 と d2 は同じです (operator==)" << std::endl; // 出力される
        }
        if (d1 != d3) {
            std::cout << "d1 と d3 は異なります (operator!=)" << std::endl; // 出力される
        }
        return 0;
    }
    
  • 構造体やクラスの論理的な等価性を定義するために、operator== をオーバーロードするのが標準的なC++の方法です。

C++20 の三方比較演算子 (<=>, "宇宙船演算子")

  • これにより、すべての比較演算子 (==, !=, <, >, <=, >=) をボイラープレートなしで自動生成することができます。

    #include <iostream>
    #include <compare> // std::strong_ordering のため
    
    struct PointCpp20 {
        int x;
        int y;
    
        // = default を指定するだけで、メンバーごとの比較が自動生成される
        // x, y の順に比較される
        auto operator<=>(const PointCpp20&) const = default;
    };
    
    int main() {
        PointCpp20 p1 = {1, 2};
        PointCpp20 p2 = {1, 2};
        PointCpp20 p3 = {3, 4};
        PointCpp20 p4 = {1, 3}; // p1 より y が大きい
    
        if (p1 == p2) { // operator== が自動生成される
            std::cout << "p1 と p2 は同じです (C++20)" << std::endl; // 出力される
        }
    
        if (p1 < p3) { // operator< が自動生成される
            std::cout << "p1 は p3 より小さいです (C++20)" << std::endl; // 出力される
        }
    
        if (p1 < p4) { // p1.x == p4.x なので p1.y と p4.y を比較
            std::cout << "p1 は p4 より小さいです (C++20)" << std::endl; // 出力される
        }
        return 0;
    }
    

std::memcmp はコンテナの生データを比較しますが、コンテナそのもののセマンティックな比較には不適切です。

代替手段: std::equal, ループによる要素ごとの比較, コンテナの operator==

std::equal (アルゴリズム)

  • ヘッダー: <algorithm>

    #include <iostream>
    #include <vector>
    #include <algorithm> // std::equal のため
    
    int main() {
        std::vector<int> v1 = {1, 2, 3, 4, 5};
        std::vector<int> v2 = {1, 2, 3, 4, 5};
        std::vector<int> v3 = {1, 2, 9, 4, 5};
    
        // サイズが同じであることも重要
        if (v1.size() == v2.size() && std::equal(v1.begin(), v1.end(), v2.begin())) {
            std::cout << "v1 と v2 は同じです (std::equal)" << std::endl; // 出力される
        }
    
        if (v1.size() == v3.size() && std::equal(v1.begin(), v1.end(), v3.begin())) {
            std::cout << "v1 と v3 は同じです (std::equal)" << std::endl;
        } else {
            std::cout << "v1 と v3 は異なります (std::equal)" << std::endl; // 出力される
        }
        return 0;
    }
    
  • 各要素が論理的に等しいかを判断するため、std::memcmp のようにパディングやバイトオーダーの問題を意識する必要がありません。

  • 2つの範囲の要素を、指定された述語(デフォルトは operator==)を使って比較します。

ループによる要素ごとの比較

  • 要素数が異なる場合や、特定の条件でのみ比較を続けたい場合などに適しています。

    #include <iostream>
    #include <vector>
    
    int main() {
        int arr1[] = {10, 20, 30};
        int arr2[] = {10, 20, 30};
        int arr3[] = {10, 20, 40};
    
        bool are_equal = true;
        if (sizeof(arr1) != sizeof(arr2)) { // 配列のバイトサイズが異なる
            are_equal = false;
        } else {
            for (size_t i = 0; i < sizeof(arr1) / sizeof(arr1[0]); ++i) {
                if (arr1[i] != arr2[i]) {
                    are_equal = false;
                    break;
                }
            }
        }
    
        if (are_equal) {
            std::cout << "arr1 と arr2 は同じです (手動ループ)" << std::endl; // 出力される
        }
    
        // vector の比較も同様に可能
        std::vector<double> d_vec1 = {1.1, 2.2, 3.3};
        std::vector<double> d_vec2 = {1.1, 2.2, 3.3};
        bool d_vec_equal = true;
        if (d_vec1.size() != d_vec2.size()) {
            d_vec_equal = false;
        } else {
            for (size_t i = 0; i < d_vec1.size(); ++i) {
                if (d_vec1[i] != d_vec2[i]) {
                    d_vec_equal = false;
                    break;
                }
            }
        }
        if (d_vec_equal) {
            std::cout << "d_vec1 と d_vec2 は同じです (手動ループ)" << std::endl; // 出力される
        }
        return 0;
    }
    
  • 最も直接的で、カスタマイズ性が高い方法です。

コンテナの operator==

  • これらの演算子は、コンテナのセマンティクスに基づいて比較を実行します(例: std::vector は要素数と各要素の等価性を比較します)。

    #include <iostream>
    #include <vector>
    #include <map>
    
    int main() {
        std::vector<int> v1 = {1, 2, 3};
        std::vector<int> v2 = {1, 2, 3};
        std::vector<int> v3 = {1, 2, 3, 4}; // 要素数が異なる
    
        if (v1 == v2) { // std::vector の operator==
            std::cout << "v1 と v2 は同じです (vector::operator==)" << std::endl; // 出力される
        }
    
        if (v1 != v3) {
            std::cout << "v1 と v3 は異なります (vector::operator!=)" << std::endl; // 出力される
        }
    
        std::map<int, std::string> m1 = {{1, "one"}, {2, "two"}};
        std::map<int, std::string> m2 = {{1, "one"}, {2, "two"}};
        std::map<int, std::string> m3 = {{1, "one"}, {3, "three"}};
    
        if (m1 == m2) { // std::map の operator==
            std::cout << "m1 と m2 は同じです (map::operator==)" << std::endl; // 出力される
        }
    
        if (m1 != m3) {
            std::cout << "m1 と m3 は異なります (map::operator!=)" << std::endl; // 出力される
        }
        return 0;
    }
    
  • std::vector, std::array, std::list, std::map などの標準コンテナは、その内容を比較する operator== を提供しています。