2009-10-15 12 views
6

古典的なmemcpyは関数の引数としてCの配列を持っています。下で指摘したように、私は自分のコードに誤りがありますが、間違ったコードはローカルコンテキストで動作しました!memcpyが単純なオブジェクトのローカル配列メンバにコピーできないのはなぜですか?

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

Memcpyは2つのローカルStr255アレイで正常に動作します。

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

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

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 
} 
+0

どのように失敗しますか? – sharptooth

+0

私が言ったように、私はmemcpyを使用するとゴミで上書きされます –

+3

バグは決してコンパイラにはありません。 –

答えて

7

のようにこれはおそらく(私の意見では)それが悪いアイデアだ理由の良い例です。配列タイプはtypedefです。

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

これら二つの断片は等価である:

typedef unsigned char Str[257]; 
Str src = "blah"; 
Str dst; 
memcpy(&dst, &src, sizeof(Str)); // unconventional 
この後者の場合 &dst
unsigned char src[257] = "blah"; 
unsigned char dst[257]; 
memcpy(&dst, &src, sizeof(unsigned char[257])); // unconventional 

&srcunsigned char (*)[257]両方のであるが、これらのポインタの値がポインタの値と同じです各配列の最初の要素は、dstsrcがこのようにmemcpyに直接渡されると崩壊するものです。元のポインタの種類は重要ではありませんので、

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

memcpyは、唯一のそれらの値をvoid*引数を取ります。

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

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

理由:パラメータ宣言のためのルールの

fnためのこれらの宣言は全て等価である、(任意の又は不特定のサイズの配列型は、同等のポインタ型に調整されます)

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)); 
} 
+0

Gack!私はCニュアンスが嫌いです。生のポインタ/配列の慣用法に取り組まなければならなかったのは、長年のことでした。 私は通常この種のポートを使っていますが、私はC++を使い、Str255は演算子を使って愚かさを捉えるクラスになります。残念ながら、今度は、K&R宣言をANSIに変換した後でも、C++でコンパイルされないバックエンドコードのヒープがあるため、プレーンCを使用する必要があります。他のプロジェクトの理由から、コードを元のものからあまりにも遠くに変更することはできません。 –

+0

Andy Dent、あなたが良いcを書いたら、あなたはそのような誤解を避けることに自信があります。あなたが良いC++を書いたのなら、単純なtypedefの代わりに "struct Str"を使い、StrCopy(struct Str * p、struct Str *から)を書いて、Cを書いたときにOOPとカプセル化の意味を言うでしょう。 – Test

+0

" C " - 私はそれを移植している、私はそれを書いていない! typedefである元のApple定義を引き継いだものには構造体を使用できません。私が言ったように、通常、このようなコードを移植するときは、オブジェクトと演算子のオーバーロードを使用して堅牢な型を取得します。 –

5

memcpy(mStr255, s, sizeof(Str255));と書いてください。なし '&'。 Str255はすでにポインタです。すなわち、C++標準の4.2によると次のとおり

左辺値またはタイプ「NTのアレイ」または「Tの結合未知の配列」の右辺値「T.へのポインタ」型の右辺値に変換することができる結果であります配列の最初の要素へのポインタ。

どうしてどこかで動作しますか? 2つの異なるポインタ(mStr255&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; 
} 

をあなたは書き込みvoid f(Str255 a)ある場合、それは第二の場合と同じです。

+0

はいを​​、trompingい疑います私はこのコードを memcpy(&a [0]、&b [1]、somesize)から切り捨ててコピーしています。 –

+0

ローカル変数の場合、未使用スタックを上書きしています。 ..usedスタックスペース| Blaha | dest |未使用スタックスペース...]、つまり、destの位置から未使用方向に向かって257バイトを書きました。 – Test

+0

printf( "%p、%p、%p、%p \ n"、&dest、&Baha、dest、Baha)を追加して私たちと共有することによって、destとdestの値(スタック)を把握してみてください。 – Test

-3

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

あなたはStr255 mStr()とプライベート宣言を交換する必要があります、またはあなたは、コンストラクタで何かをする必要があるかもしれません、mStr = new Str255()

+0

はい、あなたは錆びています。 Str255は昔ながらのC配列なので、本質的にスペースを割り当てます。 –

+0

元のポストのコードスニペットのメンバー "Str255 mStr255"は、HasMemberStr255インスタンス用に割り当てられたメモリ内でメモリを使用する "unsigned char mStr255 [255]"に展開できます。 – Will

+0

ああ、訂正ありがとう! – atk

関連する問題