6

関数の引数として C 配列を使用する従来の memcpy の落とし穴。以下で指摘したように、コードにエラーがありますが、エラーのあるコードはローカル コンテキストで機能しました。

オブジェクトを使用して Macintosh の画像オペコードの再生をエミュレートしている移植作業で、この奇妙な動作に遭遇しました。私の DrawString オブジェクトは、明らかに文字列引数のコピーに失敗したため、再生時にガベージを描画していました。以下は私が書いたテストケースです - 手動コピーループがどのように機能するかに注意してください。しかし、memcpy は失敗します。Visual Studio デバッガーでトレースすると、memcpy が宛先をガベージで上書きすることが示されます。

2 つのローカル Str255 配列の Memcpy は正常に動作します。

それらの 1 つがスタック上のオブジェクトのメンバーである場合、失敗します (他のテストでは、オブジェクトがヒープ上にある場合にも失敗します)。

次のサンプル コードは、memcpy が operator= で呼び出されることを示しています。コンストラクターで失敗した後、そこに移動しましたが、違いはありませんでした。

typedef unsigned char Str255[257];

// snippet that works fine with two local vars
Str255 Blah("\004Blah");
Str255 dest;
memcpy(&dest, &Blah, sizeof(Str255));  // THIS WORKS - WHY HERE AND NOT IN THE OBJECT?

/*!
class to help test  CanCopyStr255AsMember
*/
class HasMemberStr255  {
public:
    HasMemberStr255()
    {
        mStr255[0] = 0;
    }

    HasMemberStr255(const Str255 s)
    {
        for (int i = 0; i<257; ++i)
        {
            mStr255[i] = s[i];
            if (s[i]==0)
                return;
        }
    }

    /// fails
    void operator=(const Str255 s)  {
        memcpy(&mStr255, &s, sizeof(Str255));
    };
    operator const Str255&() { return mStr255; }

private:
    Str255 mStr255;
};
-

/*!
Test trivial copying technique to duplicate a string
Added this variant using an object because of an apparent Visual C++ bug.
*/
void TestMacTypes::CanCopyStr255AsMember()
{
    Str255 initBlah("\004Blah");
    HasMemberStr255 blahObj(initBlah);
// using the operator= which does a memcpy fails   blahObj = initBlah;

    const Str255& dest = blahObj;  // invoke cast operator to get private back out
    CPPUNIT_ASSERT( dest[0]=='\004' );
    CPPUNIT_ASSERT( dest[1]=='B' );
    CPPUNIT_ASSERT( dest[2]=='l' );
    CPPUNIT_ASSERT( dest[3]=='a' );
    CPPUNIT_ASSERT( dest[4]=='h' );
    CPPUNIT_ASSERT( dest[5]=='\0' );  //  trailing null
}
4

3 に答える 3

9

これはおそらく (私の意見では)typedef配列型が良くない理由の良い例です。

他のコンテキストとは異なり、関数宣言では、配列型のパラメーターは常に同等のポインター型に調整されます。配列が関数に渡されると、常に最初の要素へのポインターに崩壊します。

これら 2 つのスニペットは同等です。

typedef unsigned char Str[257];
Str src = "blah";
Str dst;
memcpy( &dst, &src, sizeof(Str) ); // unconventional

unsigned char src[257] = "blah";
unsigned char dst[257];
memcpy(&dst, &src, sizeof(unsigned char[257])); // unconventional

この後者の場合&dst、 と&srcは両方とも型unsigned char (*)[257]ですが、これらのポインターの値は、各配列の最初の要素へのポインターの値とdst同じsrcですmemcpy

memcpy(dst, src, sizeof(unsigned char[257])); // more usual

memcpyは引数を取るvoid*ので、元のポインターの型は関係なく、値のみが重要です。

パラメーター宣言の規則 (任意のサイズまたは未指定のサイズの配列型は、同等のポインター型に調整される) により、これらの宣言fnはすべて同等です。

typedef unsigned char Str[257];
void fn( Str dst, Str src );

void fn( unsigned char dst[257], unsigned char src[257] );

void fn( unsigned char dst[], unsigned char src[] );

void fn( unsigned char* dst, unsigned char* src );

このコードを見ると、この場合に渡される値は、実際の配列memcpyへのポインターではなく、渡されたポインターへのポインターであることがより明らかです。unsigned char

// Incorrect
void fn( unsigned char* dst, unsigned char* src )
{
    memcpy(&dst, &src, sizeof(unsigned char[257]));
}

typedef を使用すると、エラーはそれほど明白ではありませんが、まだ存在します。

// Still incorrect
typedef unsigned char Str[257];
void fn( Str dst, Str src )
{
    memcpy(&dst, &src, sizeof(Str));
}
于 2009-10-15T06:30:23.923 に答える
5

と書くべきmemcpy(mStr255, s, sizeof(Str255));です。それなし '&'。Str255はすでにポインターです。これは、C++ 標準 4.2 に従っています。

「NT の配列」または「T の境界が不明な配列」型の左辺値または右辺値は、「T へのポインター」型の右辺値に変換できます。結果は、配列の最初の要素へのポインターです。

なぜどこかで機能するのですか?2 つの異なるポインタ ( formStr255&mStr255) があり、それらには異なる型 —unsigned char *とがありunsigned char (*)[257]ます。配列のアドレスは配列の最初の要素のアドレスと同じですが、関数に引数として渡すと、スタック上の変数のアドレスが取得されます。タイプStr255することで、違いを隠すことができます。次のサンプルを確認してください。

unsigned char Blah[10] = "\004Blah";

struct X
{
    void f1( unsigned char(&a)[10] ) // first case (1)
    {
      void* x1 = &a; // pointer to array of unsigned char
      void* x2 = a;  // pointer to unsigned char due to implicit conversion array-to-pointer
    }
    void f2( unsigned char* a )     // second case (2)
    {
      void* x1 = &a; // pointer to variable 'a' which is on the stack
      void* x2 = a;  // pointer to unsigned char
    }
    unsigned char x[10];
};

int main( int argc, char ** argv )
{
    X m;
    m.f1( Blah ); // pass by reference
    m.f2( Blah ); // implicit array-to-pointer conversion

    return 0;
}

writevoid f( Str255 a )の場合は 2 番目のケースと同じです。

于 2009-10-15T05:00:02.403 に答える
-3

私が正しく読んでいれば (そして私の C++ は少し錆びています)、あなたのクラスは実際には mStr 変数にスペースを割り当てません。プライベートセクションで宣言し(ただし、割り当てているようには見えません)、コンストラクターで最初の要素を0に初期化しますが、すべてが実際にStr255オブジェクトを構築するわけではありません。

private 宣言を に置き換えるStr255 mStr()か、コンストラクターで何かを行う必要がある場合があります。mStr = new Str255()

于 2009-10-15T05:00:42.783 に答える