42

C++ プログラマーは memset を避けるべきだということわざを聞いたことがありますが、

class ArrInit {
    //! int a[1024] = { 0 };
    int a[1024];
public:
    ArrInit() {  memset(a, 0, 1024 * sizeof(int)); }
};

上記のコードを考えると、memset を使用しない場合、[1..1024] をゼロで埋めるにはどうすればよいでしょうか?C++ の memset の何が問題なのですか?

ありがとう。

4

11 に答える 11

52

C++ ではstd::fill、またはstd::fill_nより良い選択かもしれません。なぜなら、それは汎用的であり、POD だけでなくオブジェクトでも操作できるからです。ただし、memset生のバイト シーケンスで動作するため、POD 以外の初期化には使用しないでください。いずれにせよ、型が POD の場合、最適化された実装でstd::fillは内部的に特殊化を使用して呼び出すことができます。memset

于 2009-12-29T18:14:56.670 に答える
50

問題は、組み込み型で memset() を使用することではなく、クラス (別名非 POD) 型で使用することです。これを行うと、ほとんどの場合、間違ったことを行い、致命的なことを頻繁に行います。たとえば、仮想関数テーブル ポインターを踏みにじる可能性があります。

于 2009-12-29T18:02:14.730 に答える
24

ゼロ初期化は次のようになります。

class ArrInit {
    int a[1024];
public:
    ArrInit(): a() { }
};

memset の使用に関しては、(そのようなすべての関数と同様に) 使用をより堅牢にする方法がいくつかあります: 配列のサイズと型をハードコーディングしないでください:

memset(a, 0, sizeof(a));

a追加のコンパイル時のチェックのために、実際に配列であることを確認することもできます(これsizeof(a)は理にかなっています):

template <class T, size_t N>
size_t array_bytes(const T (&)[N])  //accepts only real arrays
{
    return sizeof(T) * N;
}

ArrInit() { memset(a, 0, array_bytes(a)); }

しかし、非文字型の場合、それを使用して入力する唯一の値は 0 であり、ゼロ初期化は何らかの方法で既に利用可能になっているはずです。

于 2009-12-29T17:55:01.890 に答える
13

C++の問題点は、C の問題memset点とほとんど同じですmemsetmemset物理的なゼロ ビット パターンでメモリ領域を埋めますが、実際にはほぼ 100% のケースで、対応する型の論理ゼロ値で配列を埋める必要があります。C 言語では、整数型のメモリを適切に初期化することのみが保証されています (また、 char 型だけではなく、すべてのmemset整数型に対するその有効性は、C 言語仕様に追加された比較的最近の保証です)。浮動小数点値を適切にゼロに設定することは保証されておらず、適切なヌルポインターを生成することも保証されていません。

もちろん、上記は過度に衒学的に見えるかもしれません。なぜなら、特定のプラットフォームでアクティブな追加の標準と慣習が の適用範囲を拡張する可能性があるためです (そして、最も確実にそうなるでしょう) memset。本当に必要な場合を除き、他の標準や規則に依存しないでください。C++ 言語 (および C) には、適切な型の適切なゼロ値を使用して集計オブジェクトを安全に初期化できる言語レベルの機能がいくつか用意されています。他の回答では、これらの機能について既に言及されています。

于 2009-12-29T18:34:08.690 に答える
7

意図を実装していないため、「悪い」です。

あなたの意図は、配列内の各値をゼロに設定することであり、プログラムしたことは生メモリの領域をゼロに設定することです。はい、2 つのことは同じ効果がありますが、各要素をゼロにするコードを単純に記述する方が明確です。

また、おそらく効率的ではありません。

class ArrInit
{
public:
    ArrInit();
private:
    int a[1024];
};

ArrInit::ArrInit()
{
    for(int i = 0; i < 1024; ++i) {
        a[i] = 0;
    }
}


int main()
{
    ArrInit a;
}

最適化をオンにして Visual C++ 2008 32 ビットでこれをコンパイルすると、ループは次のようにコンパイルされます -

; Line 12
    xor eax, eax
    mov ecx, 1024               ; 00000400H
    mov edi, edx
    rep stosd

とにかく、memsetがコンパイルされる可能性が高いものとほぼ同じです。ただし、 memset を使用する場合、コンパイラがさらに最適化を実行する範囲はありませんが、インテントを記述することで、コンパイラがさらに最適化を実行できる可能性があります。初期化は最適化することができますが、memset を使用していた場合はおそらく簡単には実行できませんでした。

于 2009-12-29T18:06:24.520 に答える
1

これは古いスレッドですが、興味深いねじれがあります。

class myclass
{
  virtual void somefunc();
};

myclass onemyclass;

memset(&onemyclass,0,sizeof(myclass));

完全にうまく機能します!

でも、

myclass *myptr;

myptr=&onemyclass;

memset(myptr,0,sizeof(myclass));

実際、virtuals (つまり、上記の somefunc()) を NULL に設定します。

memset は、大規模なクラスのすべてのメンバーを 0 に設定するよりも大幅に高速であるため、上記の最初の memset を何年にもわたって実行してきましたが、問題はありませんでした。

本当に興味深い質問は、なぜそれが機能するのかということです。コンパイラが実際に仮想テーブルを超えてゼロを設定し始めると思います...何か考えはありますか?

于 2012-12-21T02:37:10.900 に答える
0

クラスに適用されたときの悪さに加えて、memsetエラーが発生しやすくなります。sizeof引数の順序を狂わせたり、その部分を忘れたりするのは非常に簡単です。コードは通常これらのエラーでコンパイルされ、静かに間違ったことをします。バグの症状はかなり後になるまで現れない可能性があり、追跡が困難になります。

memsetまた、ポインタや浮動小数点など、多くのプレーンタイプでも問題があります。一部のプログラマーは、ポインターがNULLになり、floatが0.0になると想定して、すべてのバイトを0に設定します。それは移植可能な仮定ではありません。

于 2009-12-29T19:12:00.410 に答える
0

簡単な答えは、初期サイズが 1024 の std::vector を使用することです。

std::vector< int > a( 1024 ); // Uses the types default constructor, "T()".

std::vector(size) コンストラクター (および vector::resize) がすべての要素のデフォルト コンストラクターの値をコピーするため、"a" のすべての要素の初期値は 0 になります。組み込み型 (別名組み込み型、または POD) の場合、初期値が 0 であることが保証されます。

int x = int(); // x == 0

これにより、「a」が使用する型を最小限の手間で、クラスの型に変更することもできます。

memset など、パラメーターとして void ポインター (void*) を取るほとんどの関数は、タイプ セーフではありません。このようにオブジェクトの型を無視すると、構築、破棄、コピーなど、オブジェクトが依存する傾向があるすべての C++ スタイルのセマンティクスが削除されます。memset は、抽象化に違反するクラスに関する仮定を作成します (クラスの内部にあるものを認識または気にしません)。この違反は、特に組み込み型の場合、すぐに明らかになるとは限りませんが、特にコード ベースが拡大し、所有者が変わると、バグの特定が困難になる可能性があります。memset である型が vtable (仮想関数) を持つクラスである場合、そのデータも上書きされます。

于 2012-05-23T16:35:25.847 に答える
0

とにかく誰も使用しないと人々が指摘したいくつかのケースを除いて、それを使用しない本当の理由はありませんが、memguardsなどを埋めていない限り、それを使用しても実際の利点はありません.

于 2009-12-29T21:01:45.520 に答える
0

あなたのコードは問題ありません。memset が危険な C++ の唯一の時間は、次の行に沿って何かを行うときだと思いました
YourClass instance; memset(&instance, 0, sizeof(YourClass);

コンパイラが作成したインスタンスの内部データがゼロになる可能性があると思います。

于 2009-12-29T18:01:34.683 に答える
-5

C++ では new を使用する必要があります。あなたの例のような単純な配列の場合、それを使用しても実際の問題はありません。ただし、クラスの配列があり、memset を使用してそれを初期化した場合、クラスを適切に構築することはできません。

このことを考慮:

class A {
    int i;

    A() : i(5) {}
}

int main() {
    A a[10];
    memset (a, 0, 10 * sizeof (A));
}

これらの各要素のコンストラクターは呼び出されないため、メンバー変数 i は 5 に設定されません。代わりに new を使用した場合:

 A a = new A[10];

配列内の各要素にはコンストラクターが呼び出され、i は 5 に設定されます。

于 2009-12-29T17:58:27.873 に答える