为什么 memcpy 无法复制到简单对象的本地数组成员?
使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这可能是一个很好的例子,说明为什么(在我看来)
typedef
数组类型是一个坏主意。与其他上下文不同,在函数声明中,数组类型的参数始终会调整为等效的指针类型。当数组传递给函数时,它总是衰减为指向第一个元素的指针。
这两个片段是等效的:
在后一种情况下
&dst
和&src
都是unsigned char (*)[257]
类型但这些指针的值与指向每个数组第一个元素的指针的值相同,如果直接传递给dst
和src
将衰减为代码>memcpy就像这样。memcpy
采用void*
参数,因此原始指针的类型并不重要,重要的是它们的值。由于参数声明的规则(任何或未指定大小的数组类型都会调整为等效的指针类型),这些
fn
的声明都是等效的:看这段代码,更明显的是在这种情况下,传递到 memcpy 的值是指向传递的指针的指针,而不是指向实际的 unsigned char 数组的指针。
使用 typedef 时,错误并不那么明显,但仍然存在。
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:
In this latter case
&dst
and&src
are both of typeunsigned 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 whatdst
andsrc
would decay into if passed directly intomemcpy
like this.memcpy
takesvoid*
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: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 actualunsigned char
arrays.With a typedef, the error is not so obvious, but still present.
您应该编写memcpy(mStr255, s, sizeof(Str255));。没有“&”。
Str255
已经是一个指针。这是根据 C++ 标准 4.2 的:为什么它在某个地方起作用?有两个不同的指针(对于
mStr255
和&mStr255
),并且它们具有不同的类型 —unsigned char *
和unsigned char ( *)[257]
。数组的地址与数组的地址相同数组中的第一个元素,但是当您将其作为参数传递给函数时,您将获得堆栈上变量的地址。通过输入
Str255
您可以隐藏差异。检查以下示例:当您编写
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:Why does it work somewhere? There are two different pointers (for
mStr255
and&mStr255
) and they has different types —unsigned char *
andunsigned char (*)[257]
. The address of the array is the same as the address of thefirst 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:When you are write
void f( Str255 a )
, it is equal to the second case.如果我没看错(我的 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, likemStr = new Str255()