Why doesn't memcpy copy the local element of an array of a simple object?

The classic memcpy is obtained with C arrays as function arguments. As indicated below, I have an error in my code, but the error code worked in a local context!

I just came across this weird behavior in a porting job where I emulate playing Macintosh Picture wallpapers using objects. My DrawString object was drawing garbage during playback because it apparently was not able to copy the line argument. The following is a test script that I wrote - notice how the manual copy cycle works, but memcpy fails. Tracing in the Visual Studio debugger shows that memcpy reads the destination with garbage.

Memcpy on two local Str255 arrays works fine.

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

The following code example shows that memcpy is called in operator =. I moved it there after it failed in the 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 } 
+6
c ++ visual-c ++ memcpy
source share
3 answers

This is probably a good example of why (in my opinion) this is a bad idea for typedef array types.

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

These two fragments 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 both of type unsigned char (*)[257] , but the value of these pointers matches the value of pointers to the first element of each array, which makes dst and src decays if passed directly to memcpy like this.

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

memcpy accepts void* arguments, so the types of source pointers do not matter, only their values.

Due to the rule for parameter declarations (an array type of any or undefined size is set to an equivalent pointer type), these declarations for fn 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 ​​passed to memcpy , in this case, are pointers to passed pointers, and not pointers to actual unsigned char arrays.

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

With 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)); } 
+7
source share

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

An lvalue or rvalue value of type "NT array" or "array of unknown boundary 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 is he working somewhere? There are two different pointers (for mStr255 and &mStr255 ), and they have different types - unsigned char * and unsigned char (*)[257] . The address of the array matches the address of the first element in the array, but when you pass it as an argument to a function, you will get the address of the variable on the stack. By type Str255 you hide the difference. Check out the following example:

 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 write void f( Str255 a ) , it is equal to the second case.

+5
source share

If I read correctly (and my C ++ is a little rusty), your class never allocates space for the mStr variable. You declare this (but don't highlight it) in a private section, and you initialize the first element to 0 in the constructor, but you don't see that each one actually creates a Str255 object.

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

-3
source share

All Articles