web-dev-qa-db-ja.com

C ++プログラマーはmemsetを避けるべきですか?

C++プログラマーはmemsetを避けるべきだという格言を聞きました、

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

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

ありがとう。

36
Jichao

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

44
anon

C++では、std::fillまたはstd::fill_nが一般的であり、オブジェクトとPODを操作できるため、より適切な選択となる場合があります。ただし、memsetは未加工のバイトシーケンスで動作するため、非PODの初期化には使用しないでください。いずれにしても、std::fillの最適化された実装では、タイプがPODの場合、内部で特殊化を使用してmemsetを呼び出すことができます。

49
Charles Salvia

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

_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であり、ゼロ初期化はすでに何らかの方法で利用できるはずです。

23
UncleBens

C++のmemsetの問題は、Cのmemsetの問題とほとんど同じです。memsetは、実際には実質的に100対応する型の論理ゼロ値で配列を埋める必要がある場合の割合。 C言語では、memsetは整数型のメモリを適切に初期化することだけが保証されています(そして、char型だけではなくall整数型の有効性は、 C言語仕様)。浮動小数点値を適切にゼロに設定することは保証されていません。適切なnullポインタを生成することは保証されていません。

もちろん、特定のプラットフォームでアクティブな追加の標準と規則がmemsetの適用範囲を拡張する可能性があるため(おそらく間違いなく)、上記は過度に知識が豊富であると見なされる可能性がありますが、それでもOccamのかみそりに従うことをお勧めしますここでの原則:本当に必要な場合を除いて、他の標準や規約に依存しないでください。 C++言語(およびC)は、適切な型の適切なゼロ値で集約オブジェクトを安全に初期化できるいくつかの言語レベルの機能を提供します。他の回答では、これらの機能についてすでに言及しています。

10
AnT

あなたの意図を実装していないので、それは「悪い」です。

あなたの意図は、配列の各値をゼロに設定することであり、プログラムしたのは、未加工メモリの領域をゼロに設定することです。はい、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;
}

最適化をオンにしたビジュアルc ++ 2008 32ビットでこれをコンパイルすると、ループがコンパイルされます-

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

どちらにしても、memsetがコンパイルする可能性が高いのは、まさにそのとおりです。しかし、memsetを使用する場合、コンパイラーがさらに最適化を実行するためのスコープはありませんが、意図を記述することにより、コンパイラーがさらに最適化を実行できる可能性があります。初期化は最適化することができますが、memsetを使用した場合は、簡単に初期化できませんでした。

8
jcoder

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

class myclass
{
  virtual void somefunc();
};

myclass onemyclass;

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

完璧に動作します!

しかしながら、

myclass *myptr;

myptr=&onemyclass;

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

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

Memsetは、大規模なクラスのすべてのメンバーを0に設定するよりも大幅に高速であることを考えると、上記の最初のmemsetを年齢を問わず実行しており、問題は発生していません。

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

1
user1920453

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

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

0
Adrian McCarthy

とにかく誰も使用しないだろうと人々が指摘した少数のケースを除いて、それを使用しない本当の理由はありませんが、memguardや何かを埋めているのでない限り、それを使用することには本当のメリットはありません。

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(仮想関数)を持つクラスの場合、そのデータも上書きします。

0
Kit10

あなたのコードは大丈夫です。 C++でmemsetが危険なのは、次のような場合に限られます。
YourClass instance; memset(&instance, 0, sizeof(YourClass);

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

0
rui