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

发布于 2024-08-07 07:31:55 字数 1826 浏览 4 评论 0原文

使用 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
}

Classic memcpy gotcha with C arrays as function arguments. As pointed out below, I have an error in my code but the erroneous code worked in a local context!

I just encountered this weird behaviour in a porting job, where I'm emulating the Macintosh Picture opcode playback using objects. My DrawString object was drawing garbage on playback because it apparently failed to copy the string argument. The following is a test case I wrote - note how a manual copying loop works but memcpy fails. Tracing in the Visual Studio debugger shows the memcpy ovewrites the destination with garbage.

Memcpy on two local Str255 arrays works fine.

When one of them is a member in an object on the stack, it fails (in other testing it also fails when the object is on the heap).

The following sample code shows the memcpy being invoked in an operator=. I moved it there after it failed in a constructor but there was no difference.

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浪漫之都 2024-08-14 07:31:55

这可能是一个很好的例子,说明为什么(在我看来)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));
}

This is probably a good example of why (in my opinion) it's a bad idea to typedef array types.

Unlike in other contexts, in function declarations a parameter of array type is always adjusted to an equivalent pointer type. When an array is passed to the function it always decays into a pointer to the first element.

These two snippets are equivalent:

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

In this latter case &dst and &src are both of type unsigned char (*)[257] but the value of these pointers are the same as the value of pointers to the first element of each array, which is what dst and src would decay into if passed directly into memcpy like this.

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

memcpy takes void* arguments so the types of the original pointers don't matter, only their values.

Because of the rule for parameter declarations (an array type of any or unspecified size is adjusted to the equivalent pointer type), these declarations for fn are all equivalent:

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 );

Looking at this code, it is more obvious that the values being passed into memcpy in this case are pointers to the passed pointers, and not pointers to the actual unsigned char arrays.

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

With a typedef, the error is not so obvious, but still present.

// Still incorrect
typedef unsigned char Str[257];
void fn( Str dst, Str src )
{
    memcpy(&dst, &src, sizeof(Str));
}
祁梦 2024-08-14 07:31:55

您应该编写memcpy(mStr255, s, sizeof(Str255));。没有“&”。 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 ) 时,它等于第二种情况。

You should write memcpy(mStr255, s, sizeof(Str255));. Without '&'. Str255 is already a pointer. That's according to C++ Standard 4.2:

An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to an rvalue of type “pointer to T.” The result is a pointer to the first element of the array.

Why does it work somewhere? There are two different pointers (for mStr255 and &mStr255) and they has different types — unsigned char * and unsigned char (*)[257]. The address of the array is the same as the address of the
first element in the array, but when you pass it as argument to a function you will get address of variable on the stack. By typefing Str255 you are hide the difference. Check the following sample:

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;
}

When you are write void f( Str255 a ), it is equal to the second case.

后eg是否自 2024-08-14 07:31:55

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

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

If I'm reading correctly (and my C++ is a little rusty), your class never actually allocates space for the mStr variable. You declare it (but don't appear to allocate it) in the private section, and you initialize the first element to 0 in the constructor, but you don't appear to every actually construct a Str255 object.

You may need to replace the private declaration with Str255 mStr(), or you may need to do something in the constructor, like mStr = new Str255()

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文