6

使用 C 数组作为函数参数的经典 memcpy 陷阱。正如下面所指出的,我的代码中有一个错误,但错误的代码在本地上下文中工作!

我刚刚在移植工作中遇到了这种奇怪的行为,我正在使用对象模拟 Macintosh 图片操作码播放。我的 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
}
4

3 回答 3

9

这可能是一个很好的例子,说明为什么(在我看来)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,and&src都是类型unsigned char (*)[257],但这些指针的值与指向每个数组的第一个元素的指针的值相同,如果像这样直接传递进去,它就会dst衰减。srcmemcpy

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的指针”类型的右值。结果是指向数组第一个元素的指针。

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

于 2009-10-15T05:00:02.403 回答
-3

如果我阅读正确(而且我的 C++ 有点生疏),那么您的类实际上从未为 mStr 变量分配空间。您在私有部分中声明它(但似乎没有分配它),并在构造函数中将第一个元素初始化为 0,但您似乎并不是每个实际构造了一个 Str255 对象。

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

于 2009-10-15T05:00:42.783 回答