When Is a Type in C++11 Allowed to Be Memcpyed

When is a type in c++11 allowed to be memcpyed?

You can copy an object of type T using memcpy when is_trivially_copyable<T>::value is true. There is no particular need for the type to be a standard layout type. The definition of 'trivially copyable' is essentially that it's safe to do this.

An example of a class that is safe to copy with memcpy but which is not standard layout:

struct T {
int i;
private:
int j;
};

Because this class uses different access control for different non-static data members it is not standard layout, but it is still trivially copyable.

Can I memcpy() any type which has a trivial destructor?

No. The requirement is that the type be trivially copyable (§3.9/2) which has a few more requirements, like the lack of a non-trivial copy constructor (§9/6).

A trivially copyable class is a class that:

— has no non-trivial copy constructors (12.8),

— has no non-trivial move constructors (12.8),

— has no non-trivial copy assignment operators (13.5.3, 12.8),

— has no non-trivial move assignment operators (13.5.3, 12.8), and

— has a trivial destructor (12.4).

So you should use is_trivially_copyable instead.

C++ guarantee and name for POD-like data, memcpy capable

In C++0x, the concept of PODness is broken out into several individually useful categories:

A trivially copyable class is a class that (draft 3242, section [class]):

  • has no non-trivial copy constructors (12.8),
  • has no non-trivial move constructors (12.8),
  • has no non-trivial copy assignment operators (13.5.3, 12.8),
  • has no non-trivial move assignment operators (13.5.3, 12.8), and
  • has a trivial destructor (12.4).

A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable.

[ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base
classes. — end note ]

A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with
    non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

The requirements for trivial constructors, assignment operators, and destructor are scattered throughout section 12 "Special Member Functions" [special].

Is memcpy(&a + 1, &b + 1, 0) defined in C11?

C11 says:

(C11, 7.24.2.1p2) "The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1."

&a + 1 itself is a valid pointer to integer addition but &a + 1 is not a pointer to an object, so the call invokes undefined behavior.

Is std::memcpy between different trivially copyable types undefined behavior?

The standard may fail to say properly that this is allowed, but it's almost certainly supposed to be, and to the best of my knowledge, all implementations will treat this as defined behaviour.

In order to facilitate the copying into an actual char[N] object, the bytes making up the f object can be accessed as if they were a char[N]. This part, I believe, is not in dispute.

Bytes from a char[N] that represent a uint32_t value may be copied into an uint32_t object. This part, I believe, is also not in dispute.

Equally undisputed, I believe, is that e.g. fwrite may have written the bytes in one run of the program, and fread may have read them back in another run, or even another program entirely.

Because of that last part, I believe it does not matter where the bytes came from, as long as they form a valid representation of some uint32_t object. You could have cycled through all float values, using memcmp on each until you got the representation you wanted, that you knew would be identical to that of the uint32_t value you're interpreting it as. You could even have done that in another program, a program that the compiler has never seen. That would have been valid.

If from the implementation's perspective, your code is indistinguishable from unambiguously valid code, your code must be seen as valid.

Can I use memcpy in C++ to copy classes that have no pointers or virtual functions

According to the Standard, if no copy constructor is provided by the programmer for a class, the compiler will synthesize a constructor which exhibits default memberwise initialization. (12.8.8) However, in 12.8.1, the Standard also says,

A class object can be copied in two
ways, by initialization (12.1, 8.5),
including for function argument
passing (5.2.2) and for function value
return (6.6.3), and by assignment
(5.17). Conceptually, these two
operations are implemented by a copy
constructor (12.1) and copy assignment
operator (13.5.3).

The operative word here is "conceptually," which, according to Lippman gives compiler designers an 'out' to actually doing memberwise initialization in "trivial" (12.8.6) implicitly defined copy constructors.

In practice, then, compilers have to synthesize copy constructors for these classes that exhibit behavior as if they were doing memberwise initialization. But if the class exhibits "Bitwise Copy Semantics" (Lippman, p. 43) then the compiler does not have to synthesize a copy constructor (which would result in a function call, possibly inlined) and do bitwise copy instead. This claim is apparently backed up in the ARM, but I haven't looked this up yet.

Using a compiler to validate that something is Standard-compliant is always a bad idea, but compiling your code and viewing the resulting assembly seems to verify that the compiler is not doing memberwise initialization in a synthesized copy constructor, but doing a memcpy instead:

#include <cstdlib>

class MyClass
{
public:
MyClass(){};
int a,b,c;
double x,y,z;
};

int main()
{
MyClass c;
MyClass d = c;

return 0;
}

The assembly generated for MyClass d = c; is:

000000013F441048  lea         rdi,[d] 
000000013F44104D lea rsi,[c]
000000013F441052 mov ecx,28h
000000013F441057 rep movs byte ptr [rdi],byte ptr [rsi]

...where 28h is the sizeof(MyClass).

This was compiled under MSVC9 in Debug mode.

EDIT:

The long and the short of this post is that:

1) So long as doing a bitwise copy will exhibit the same side effects as memberwise copy would, the Standard allows trivial implicit copy constructors to do a memcpy instead of memberwise copies.

2) Some compilers actually do memcpys instead of synthesizing a trivial copy constructor which does memberwise copies.

How do I determine if a type is memcpy-save?

The correct type trait to use for this is std::is_trivially_copyable, not std::is_trivially_copy_assignable.

To fix the warning, use if constexpr instead of if in order to perform the check at compile time and only generate one of the two branches as unconditional logic for a given type T. Even if an ill-formed call is unreachable due to runtime conditional logic, the warning is emitted by the compiler because the call still exists in the generated code.

Also consider using std::copy_n from <algorithm> to simplify your logic for the fallback.

Try it on godbolt.org: Demo.

For C++11, you can use std::enable_if to select which implementation to use at compile time similar to C++17 if constexpr:

template<class T>
typename std::enable_if<std::is_trivially_copyable<T>::value>::type
fancy_copy(const T* src, T* dest, unsigned int size)
{
memcpy(dest, src, sizeof(T) * size);
}

template<class T>
typename std::enable_if<!std::is_trivially_copyable<T>::value>::type
fancy_copy(const T* src, T* dest, unsigned int size)
{
std::copy_n(src, size, dest);
}

Try it on godbolt.org: Demo.

Ultimately though, as others have pointed out, this fancy_copy might be a premature optimization, and you would be better off just using std::copy_n where semantically correct, allowing the compiler to perform its own optimizations. Compare the binary between fancy_copy and std::copy_n when using -O3 to see for yourself. They're exactly identical.



Related Topics



Leave a reply



Submit