Visaul Studio 2005 の vector::operator [] のおせっかい

はじめに 7/10追記

int  anArray[9];
int* p1 = &anArray[9];                   // ダメ
int* p2 = anArray + ARRAY_SIZE(anArray); // OK

との事です。ダイレクトに終端のアドレス取るのではなくて、配列の先頭からn番目とするのが正しいようです。

というわけで、下の記事は間違ってるようでした。すんません。^^;

一部正しくない過去記事

今まで動いてたコードがどーにもこーにも落ちる。 追ってみると、vector::operator () の中で落ちていました。

…はて? VS2003 の頃はしっかり動いてたから、不正な番地にはアクセスしてないハズ。なんでやろ…という感じで vector::operator の中を追ってみたら…


    reference operator[](size_type _Pos)
        {   // subscript mutable sequence

 #if _HAS_ITERATOR_DEBUGGING
        if (size() <= _Pos)
            {
            _DEBUG_ERROR("vector subscript out of range");
            _SCL_SECURE_OUT_OF_RANGE;
            }
 #endif /* _HAS_ITERATOR_DEBUGGING */
        _SCL_SECURE_VALIDATE_RANGE(_Pos < size());

        return (*(_Myfirst + _Pos));
        }

とまぁ、範囲チェックされるようになってました。

「おまえ、やっぱり不正な index にアクセスしてるんじゃーん!ぷっぷ〜」とか指摘されそうですが、ちゃうんですよちゃうんですよ!!


例えば


struct SDoll
{
    tstring strDollName;
    tstring strSerifSuffix;
};

// 配列の中身を表示
void disp(const SDoll* psDollList, const size_t nListSize)
{
    for(size_t i = 0; i < nListSize; ++i)
    {
        tcout << _T("DollNumber  : ") << i                            << endl
              << _T("DollName    : ") << psDollList[i].strDollName    << endl
              << _T("SerifSuffix : ") << psDollList[i].strSerifSuffix << endl
              << endl;
    }
}

void func()
{
    vector<SDoll>   stlDolls;

    foo(stlDolls); // stlDolls の中身を弄くる何か

    disp(&stlDolls[0], stlDolls.size());
}

こんなコードがあったとしましょう。

foo() で stlDolls に要素が代入された場合は、VS2005(のdebug build)でも正常の動作しますが、要素が何も代入されなかった場合、0番目には要素が無い == 範囲エラーで落ちてしまいます。

とはいえ、このコードの場合 disp() の nListSize に 0 を投げたとしても正常に動作するわけです。例え psDollList に不正なアドレスが代入されていたとしても、結局利用されない訳ですから。

不正なアドレスを取る方がおかしいんちゃうかなー とか思われてしまいそうですが、STLなんかの end() は *アクセスすると* 落ちてしまう iterator(要素の終わりの1コ次ぎを示すもの)を平気で返してくる訳ですし、アルゴリズム系の関数も要素の終端を識別する為にこんな iterator が来る事を普通に期待してたりしますし。↓見たいに。

int aa[10];
int ab[10];

// aa + 10 は不正なアドレスだけど、正常に動作する
std::copy(aa, aa + 10, ab);


// VS2005(のDebugBuild)から「va[10] は範囲外」ってことで
// 落ちることになる
std::vector va(10);
std::copy(&va[0], &va[10], ab);


ほいじゃぁ、解決策はー? って感じですが、

&stlDolls[0] の代りに(カッコ悪いけど)&*stlDolls.begin() 使えばOK…かと思ったら vector::iterator::operator * () *1 でやっぱり範囲チェックが入るようなのでダメぽ。

となると、如何にも 「範囲チェック要らない時はプリプロセッサでばっさり削除しますね」 的に用意されている _HAS_ITERATOR_DEBUGGING

#define _HAS_ITERATOR_DEBUGGING 0

なんて定義してやればOKなんでしょう。 …が、

こうすると、全ての vector(とかのコンテナ)で範囲チェックが off になるので、本当に範囲外エラーが起きるようなコード書いちゃった時が怖いよなー とか思うわけで…。(ぉ ^^;


んまぁーでも、今までも範囲チェックが無い状態で書いてきたんですから、#define で定義しちゃうのが妥当かしら〜。 範囲チェックを行いつつ関数は vector::at だった気がするしのぅ。

追記

すぐ下の _SCL_SECURE_VALIDATE_RANGE なんちゅーのでも範囲チェックしてるやん! _HAS_ITERATOR_DEBUGGING を 0 にするだけじゃダメでした orz

ちゅーわけで _SCL_SECURE_VALIDATE_RANGE も off にする為に

#define _HAS_ITERATOR_DEBUGGING 0

いやまー、範囲チェックは重要だとは思うんですけどねー。ねー。ねー。

*1:std::_Vector_const_iterator とかいうのになったのね