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

And*_*ent 6 c++ memcpy visual-c++

使用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
}
Run Code Online (Sandbox Code Playgroud)

CB *_*ley 7

这可能是为什么(在我看来)typedef数组类型不好的一个很好的例子.

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

这两个片段是等效的:

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

在后一种情况下&dst,&src它们都是类型,unsigned char (*)[257]但这些指针的值与指向每个数组的第一个元素的指针的值相同,如果直接传递到这样的话,这将是什么dst并且src会衰减memcpy.

memcpy(dst, src, sizeof(unsigned char[257])); // more usual
Run Code Online (Sandbox Code Playgroud)

memcpy接受void*参数,因此原始指针的类型无关紧要,只有它们的值.

由于参数声明的规则(任何或未指定大小的数组类型被调整为等效的指针类型),这些声明fn都是等效的:

typedef unsigned char Str[257];
void fn( Str dst, Str src );
Run Code Online (Sandbox Code Playgroud)
void fn( unsigned char dst[257], unsigned char src[257] );
Run Code Online (Sandbox Code Playgroud)
void fn( unsigned char dst[], unsigned char src[] );
Run Code Online (Sandbox Code Playgroud)
void fn( unsigned char* dst, unsigned char* src );
Run Code Online (Sandbox Code Playgroud)

看一下这段代码,更明显的是,memcpy在这种情况下传入的值是指向传递指针的指针,而不是指向实际unsigned char数组的指针.

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

使用typedef,错误不是那么明显,但仍然存在.

// Still incorrect
typedef unsigned char Str[257];
void fn( Str dst, Str src )
{
    memcpy(&dst, &src, sizeof(Str));
}
Run Code Online (Sandbox Code Playgroud)


Kir*_*sky 5

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

可以将"数组NT"或"未知T的数组"的左值或右值转换为"指向T的指针"的右值.结果是指向数组的第一个元素的指针.

为什么它在某处工作?有两个不同的指针(for 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;
}
Run Code Online (Sandbox Code Playgroud)

当你写作时void f( Str255 a ),它等于第二种情况.