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