为什么memcpy无法复制到简单对象的本地数组成员?

时间:2009-10-15 04:50:36

标签: c++ visual-c++ memcpy

使用C数组作为函数参数的经典memcpy问题。如下所述,我的代码中有错误,但错误的代码在本地环境中工作!

我刚刚在移植作业中遇到这种奇怪的行为,我正在使用对象模拟Macintosh Picture操作码播放。我的DrawString对象在播放时绘制了垃圾,因为它显然无法复制字符串参数。以下是我写的测试用例 - 注意手动复制循环如何工作但memcpy失败。在Visual Studio调试器中进行跟踪显示memcpy使用垃圾来覆盖目标。

两个本地Str255阵列上的Memcpy工作正常。

当其中一个成为堆栈中对象的成员时,它就会失败(在其他测试中,当对象在堆上时它也会失败)。

以下示例代码显示了在operator =中调用的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
}

3 个答案:

答案 0 :(得分:7)

这可能是一个很好的例子,为什么(在我看来)typedef数组类型是个坏主意。

与其他上下文不同,在函数声明中,数组类型的参数始终调整为等效的指针类型。当数组传递给函数时,它总是衰减成指向第一个元素的指针。

这两个片段是等效的:

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]类型,但这些指针的值与指向每个数组的第一个元素的指针的值相同,是dstsrc如果直接传递到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));
}

答案 1 :(得分:5)

你应该写memcpy(mStr255, s, sizeof(Str255));。没有'&amp;'。 Str255已经是一个指针。这是根据C ++标准4.2:

  

“NT的数组”或“T的未知边界数组”类型的左值或右值可以转换为“指向T的指针”的右值。结果是指向数组的第一个元素的指针。

为什么它在某处工作?有两个不同的指针(适用于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 )时,它等于第二种情况。

答案 2 :(得分:-3)

如果我正确阅读(并且我的C ++有点生疏),你的类实际上永远不会为mStr变量分配空间。你在private部分声明它(但似乎没有分配它),并在构造函数中将第一个元素初始化为0,但是看起来并不是每个实际构造一个Str255对象。

您可能需要将私有声明替换为Str255 mStr(),或者您可能需要在构造函数中执行某些操作,例如mStr = new Str255()