いつものようにmemsetを正しく使う

1860 ワード

前段のプロジェクトで問題が見つかりました.プログラムは常にdynamic_にあります.castが動的変換を行う際に異常が発生し,半日調べたところmemsetの使用に問題があることが判明し,問題自体は明らかであるが,数十万行のコードレベルにあると,それほど位置決めが容易ではない.
本文はmemsetを用いたいくつかの注意すべき点をまとめ,内容は簡単であるが,皆さんの役に立つことを願っている.
1.memsetはバイト単位でメモリブロックを初期化します.
バイト単位の配列を初期化する場合、memsetで各配列単位を任意の値に初期化できます.たとえば、
char data[10];
memset(data, 1, sizeof(data));    // right
memset(data, 0, sizeof(data));    // right

他のベースタイプを初期化する場合は、たとえば、
int data[10];
memset(data, 0, sizeof(data));    // right
memset(data, -1, sizeof(data));    // right
memset(data, 1, sizeof(data));    // wrong, data[x] would be 0x0101 instead of 1

2.構造体タイプにポインタが含まれている場合、memsetを使用して初期化するときは注意が必要です.
例えば次のコードでは、
struct Parameters {
          int x;
          int* p_x;
};
Parameters par;
par.p_x = new int[10];
memset(&par, 0, sizeof(par));

memsetが初期化されるとp_は初期化されませんxが指すint配列ユニットの値は、メモリが割り当てられたp_をxポインタ自体が0に設定され、メモリが漏洩します.同様にstd::vectorなどのデータ型に対してもmemsetを用いて初期化すべきではないことが明らかになった.
3.構造体またはクラス自体またはそのベースクラスに虚関数が存在する場合もmemsetを慎重に使用する必要があります.
この問題は冒頭の項目で発見された問題で、以下のコードで、
class BaseParameters
{
public:
    virtual void reset() {}
};

class MyParameters : public BaseParameters
{
public: 
    int data[3];
    int buf[3];
};

MyParameters my_pars;
memset(&my_pars, 0, sizeof(my_pars));
BaseParameters* pars = &my_pars;

//......

MyParameters* my = dynamic_cast(pars);

プログラムはdynamic_に実行されますcast時に異常が発生しました.理由は、データ構造MyParametersのdataやbufを初期化することを目的としています.通常、初期化が必要なメモリ空間はsizeof(int)*3*2=24バイトですが、memsetを使用してMyParametersタイプのデータ構造を直接初期化する場合、sizeof(my_pars)は28バイトです.マルチステートメカニズムを実現するため、C++虚関数のあるオブジェクトには虚関数テーブル(V-Table)を指すポインタが含まれ、memsetを使用するとその虚関数テーブルのポインタも0に初期化され、dynamic_castもRTTI技術を使用しており、実行時にV-Tableを使用するが、この場合V-Tableとのリンクが破壊されているため、プログラムに異常が発生する.