コンパイラエラー

C言語におけるコンパイラーエラー C3028について解説

C3028エラーは、OpenMPのreduction句で、変数やstaticデータメンバー以外のシンボルを指定したときに発生します。

たとえば、クラスの非staticメンバーや関数、名前空間を渡すとエラーが表示されます。

サンプルコードを参考に、正しい記述方法とエラーの原因を確認できます。

C3028エラーの原因

OpenMPにおけるreduction句の仕様

OpenMPのreduction句は、並列処理内で各スレッドが部分結果を持ち、最終的にそれらをひとつにまとめるために利用されます。

この句では、変数やstaticデータメンバーを対象に、累算や論理演算などの処理が正しく実行されるように設計されています。

具体的には、次の条件が求められます。

使用可能なシンボル:変数とstaticデータメンバー

・通常の変数(グローバル変数やローカル変数)

・クラスや構造体のstaticメンバー

これらは、メモリ上の固定した場所に配置されているため、複数のスレッドによる同時アクセスが容易に管理できます。

使用不可なシンボル:非staticメンバー、関数、名前空間

一方で、非staticなクラスメンバー、関数、名前空間といったシンボルをreduction句で利用すると、共有状態の管理が困難となるため、エラー C3028 が発生します。

たとえば、クラスの非staticメンバーを渡した場合、各オブジェクトに異なるアドレスが設定されるため、正しく累算処理が行われません。

OpenMPのreduction句利用時の注意点

コード例によるエラー確認

OpenMPのreduction句を利用する際に、正しい記述方法と誤った記述方法を確認することで、C3028エラーが発生する原因や対策が明確になります。

正しい記述方法

以下のサンプルコードは、グローバル変数およびstaticデータメンバーを使用した正しいreduction句の利用例です。

#include <stdio.h>
#include <omp.h>
// グローバル変数の宣言
int g_counter = 0;
class Counter {
public:
    // staticメンバーは正しく利用可能
    static int s_count;
};
int Counter::s_count = 0;
int main() {
    // 並列実行時に正しい変数とstaticメンバーを指定
    #pragma omp parallel reduction(+: g_counter, Counter::s_count)
    {
        g_counter += 1;         // 各スレッドでカウンタを加算
        Counter::s_count += 1;    // staticメンバーの加算
    }
    printf("g_counter = %d\n", g_counter);
    printf("Counter::s_count = %d\n", Counter::s_count);
    return 0;
}
g_counter = (スレッド数に依存)
Counter::s_count = (スレッド数に依存)

発生するエラー例

次のサンプルコードでは、非staticメンバーをreduction句で利用しており、C3028エラーが発生します。

#include <stdio.h>
#include <omp.h>
class MyClass {
public:
    int value;  // 非staticメンバーは使用不可
    MyClass() : value(0) {}
};
int main() {
    MyClass obj;
    // 非staticメンバーを指定するとエラーとなる
    #pragma omp parallel reduction(+: obj.value)
    {
        obj.value += 1;
    }
    printf("obj.value = %d\n", obj.value);
    return 0;
}

この例では、クラスの非staticメンバーであるvalueをreduction句に渡しているため、コンパイル時にC3028エラーが出力されます。

C++コード事例の解析

クラス内メンバーの扱い

C++では、クラスのメンバーについてもreduction句を適用する場合、staticかどうかの違いが重要です。

staticメンバーの場合

staticメンバーはクラス全体で一つのインスタンスとして保持されるため、reduction句に指定しても問題が生じません。

次の例では、staticメンバーを正しく利用しています。

#include <iostream>
#include <omp.h>
class MyClass {
public:
    // staticメンバーはクラス全体で共有される
    static int s_total;
};
int MyClass::s_total = 0;
int main() {
    // 正しいreduction句の指定:staticメンバーを使用
    #pragma omp parallel reduction(+: MyClass::s_total)
    {
        MyClass::s_total += 1;  // 各スレッドでカウントアップ
    }
    std::cout << "MyClass::s_total = " << MyClass::s_total << std::endl;
    return 0;
}
MyClass::s_total = (スレッド数に依存)

非staticメンバーの場合

以下の例のように、非staticメンバーをreduction句に指定すると、各オブジェクトの値が個別に管理されるため、コンパイラーエラー C3028 が発生します。

#include <iostream>
#include <omp.h>
class MyClass {
public:
    int count;  // 非staticメンバー
    MyClass() : count(0) {}
};
int main() {
    MyClass obj;
    // 非staticメンバーを指定するとエラー(例示のため意図的に記述)
    #pragma omp parallel reduction(+: obj.count)
    {
        obj.count += 1;
    }
    std::cout << "obj.count = " << obj.count << std::endl;
    return 0;
}

このコードでは、obj.countが非staticなためエラーとなります。

その他のシンボル事例

OpenMPのreduction句では、関数や名前空間も対応していません。

一例として以下の内容を示します。

関数および名前空間のケース

関数名や名前空間をreduction句に指定すると、具体的なデータが存在しないため、エラーが発生します。

次のコードはその例です。

#include <stdio.h>
#include <omp.h>
namespace ExampleNS {
    // 名前空間内にデータが存在しても、名前空間そのものは使用不可
    int data = 0;
}
// 関数も対象外
void increment() {
    // 何もしない関数
}
int main() {
    // 名前空間をreduction句に指定するとエラー
    #pragma omp parallel reduction(+: ExampleNS)
    {
        ExampleNS::data += 1;
    }
    // 関数を指定するとエラーとなるため、次のコードは意図的な誤記例です
    #pragma omp parallel reduction(+: increment)
    {
        // 関数呼び出しはreduction句には使用できない
        increment();
    }
    printf("ExampleNS::data = %d\n", ExampleNS::data);
    return 0;
}

この例では、名前空間ExampleNSおよび関数incrementをreduction句に指定しており、どちらも使用できないためコンパイルエラーとなります。

エラー解決のための対策

コード修正のポイント

エラー C3028 を回避するためには、reduction句に渡す変数が適切なシンボルであるか確認することが重要です。

以下のポイントに留意してください。

注意すべき詳細点

・reduction句には、必ず通常の変数またはstaticメンバーを指定する

・クラスの非staticメンバーや関数、名前空間は対象外となる

・必要に応じて、対象のメンバーをstaticに変更する、もしくは別途共有変数を用意することで、問題を回避することが可能です

サンプルコードの比較

次の比較例では、誤った記述と正しい記述の違いを示しています。

誤った記述:非staticメンバーを利用

#include <iostream>
#include <omp.h>
class Accumulator {
public:
    int sum;  // 非staticなメンバー変数
    Accumulator() : sum(0) {}
};
int main() {
    Accumulator acc;
    // 非staticメンバーである acc.sum を指定するとエラーが発生する
    #pragma omp parallel reduction(+: acc.sum)
    {
        acc.sum += 1;
    }
    std::cout << "acc.sum = " << acc.sum << std::endl;
    return 0;
}

正しい記述:staticメンバーを利用

#include <iostream>
#include <omp.h>
class Accumulator {
public:
    // staticメンバーに変更することで、全てのスレッドで共有が可能となる
    static int sum;
};
int Accumulator::sum = 0;
int main() {
    // staticメンバーである Accumulator::sum を指定し、正しくreduction句を使用
    #pragma omp parallel reduction(+: Accumulator::sum)
    {
        Accumulator::sum += 1;
    }
    std::cout << "Accumulator::sum = " << Accumulator::sum << std::endl;
    return 0;
}
Accumulator::sum = (スレッド数に依存)

実践例の紹介

以下のサンプルコードは、正しくreduction句を利用してエラー C3028 を回避した実践例です。

#include <iostream>
#include <omp.h>
// グローバル変数はreduction対象として問題なく利用可能
int g_total = 0;
class Data {
public:
    // staticメンバー変数の利用例
    static int s_total;
};
int Data::s_total = 0;
int main() {
    // 並列化してグローバル変数とstaticメンバーに対して正しくreductionを適用
    #pragma omp parallel reduction(+: g_total, Data::s_total)
    {
        // 各スレッドが変数に対して加算処理を行う
        g_total += 2;
        Data::s_total += 2;
    }
    std::cout << "g_total = " << g_total << std::endl;
    std::cout << "Data::s_total = " << Data::s_total << std::endl;
    // OpenMPの並列処理を終了するためのmain関数の戻り値
    return 0;
}
g_total = (スレッド数&times2に依存)
Data::s_total = (スレッド数&times2に依存)

この実践例では、グローバル変数g_totalとクラスのstaticメンバーData::s_totalを対象として、各スレッドが値を累算する処理が問題なく実行されます。

以上の対策を参考に、コード修正を行うことでC3028エラーを回避できます。

まとめ

本記事では、OpenMPのreduction句利用時に発生するコンパイラーエラー C3028 の原因と対策について解説しています。

変数やstaticメンバーは正しく利用できる一方で、非staticメンバー、関数、名前空間は対象外となるためエラーとなります。

正しい記述方法と誤った記述例、さらには正しいコード修正のポイントを具体例を通して紹介しています。

関連記事

Back to top button
目次へ