2

For a trivially copyable type T consider:

void f(T z)
{
   T a;
   T b;
   std::memcpy(&b, &a, sizeof(T));

   a = z;
   b = z;

   // ...
}

Is the behavior of this fragment defined in C++14 if

  1. T is char,
  2. T is int, or
  3. T is struct { int data; }; ?

Assume that f gets passed an object that holds a valid value.

Do the answers change if the call to memcpy is replaced by copy assignment b = a?

Can the results be carried over to copy construction T(a) and move construction/assignment?


Note: This question, in contrast to What are Aggregates and PODs and how/why are they special?, is particularly concerned with the corner case of copying indeterminate values.

Community
  • 1
  • 1
precarious
  • 598
  • 2
  • 14
  • what is "trivially copyable" in this context? – shafeen Oct 07 '15 at 16:18
  • @shafeen I'm referring to the TriviallyCopyable concept. Link added. – precarious Oct 07 '15 at 16:22
  • 1
    Following the link in the very reference you provided [Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy...](http://en.cppreference.com/w/cpp/types/is_trivially_copyable) – StoryTeller - Unslander Monica Oct 07 '15 at 16:26
  • This "question" seems to constitute several different questions. I think it would be wise to either separate them or number them explicitly. You say "for a trivially copyable type T" but then you specify possible values for T. Which is it? A general question regarding trivially copyable types, or a question regarding those specific three types? – davmac Oct 07 '15 at 16:34
  • Do we still have POD in C++14? – user3528438 Oct 07 '15 at 16:40
  • 1
    Possible duplicate of [What are Aggregates and PODs and how/why are they special?](http://stackoverflow.com/questions/4178175/what-are-aggregates-and-pods-and-how-why-are-they-special) – user3528438 Oct 07 '15 at 16:45
  • 4
    Warning: Do Not depend upon the traits `std::is_trivially_copyable` or `std::is_trivial` to decide if this is safe. These traits will not always give you the correct answer. They can answer true for non-copyable, non-movable types such as `atomic`. Instead rely on the `std::is_trivially_copy_constructible`, `std::is_trivially_copy_assignable` family of traits which inspect the triviality of each special member individually. – Howard Hinnant Oct 07 '15 at 18:01
  • Also relevant [“constructing” a trivially-copyable object with memcpy](http://stackoverflow.com/q/30114397/1708801) – Shafik Yaghmour Oct 07 '15 at 19:51

2 Answers2

4

There are a couple things at play here:

  • an expression evaluating to an indeterminate value causes undefined behavior, with certain exceptions (8.5p12)
  • unsigned char (and possibly char, if unsigned) is the exception
  • variables with automatic storage duration and whose types have trivial default initialization initially have indeterminate values (5.3.4p17)

This means that

  • unsigned char is fine, no matter whether using memcpy or memmove or copy-assignment or copy-constructor
  • memcpy and memmove is presumably fine for all types, because the result is not "produced by an evaluation" (to meet this requirement, an implementation can use unsigned char internally, or take advantage of implementation-specific guarantees made for other types)
  • copy-constructor and copy-assignment for other types will fail if the right-hand-side is an indeterminate value

Of course, even the valid methods for copying an indeterminate value create another indeterminate value.


Paragraph numbers correspond to draft n4527

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I think your answer is probably completely correct, but it would be nice if you could quote relevant parts of the standard. – davmac Oct 07 '15 at 16:46
  • From [cppreference.com](http://en.cppreference.com/w/cpp/language/copy_constructor#Trivial_copy_constructor): "Objects with trivial copy constructors can be copied by copying their object representations manually, e.g. with `std::memmove`." – precarious Oct 07 '15 at 16:48
  • @precarious: Not sure how that is relevant to indeterminate values. – Ben Voigt Oct 07 '15 at 16:50
  • @BenVoigt: Not sure as well ;-). Do you have a reference that copying indeterminate values is invalid? – precarious Oct 07 '15 at 16:52
  • @precarious he's given it already - 8.5p12. (That is, when you take into account that copying by assignment uses an expression which produces the indeterminate value.) – davmac Oct 07 '15 at 16:54
  • Is copying an evaluation? – precarious Oct 07 '15 at 17:05
  • @precarious: The initializer (in copy construction), or right-hand side of a copy assignment, gets evaluated. (The Standard talks about "the value of the expression", which is what *evaluation* retrieves) – Ben Voigt Oct 07 '15 at 17:15
  • Does this imply that copy assigning/constructing partially initialized objects is invalid? E.g. `struct A{ int i; int j; }; A a; a.i = 0; A b(a);` Doesn't this unnecessarily restrict the usefulness of POD? – precarious Oct 07 '15 at 17:35
  • @precarious: On Itanium, that goes boom. If you use `float` or `double` instead of `int`, it can also go boom on x86. – Ben Voigt Oct 07 '15 at 17:51
  • It cannot go boom if copy construction is implemented as a `memcpy` (which does not seem to be forbidden by the standard) or am I missing the point? I'm tending to accept your answer since the standard does not explicity endorse copy assigning indeterminate values. I think that the value of an expression can be copied without evaluation as long as no calculation is involved. – precarious Oct 07 '15 at 18:23
  • 2
    @precarious No, under the standard, assignment is an evaluation. The standard places no restrictions on the behavior of ill-formed programs. One such behavior would be doing what *you* expect consistently and every time. Another such behavior would be to detect that one such branch of code does this operation and logically deduce that that branch will not be followed, eliminating tests that lead to that branch during optimization. Note that gcc can do this when your code relies on signed integer overflow "doing what you expect the hardware to do": it is UB, so they optimize it away. – Yakk - Adam Nevraumont Oct 08 '15 at 14:19
  • @Yakk Thank you for explaining the issue in detail! – precarious Oct 08 '15 at 20:08
3

Yes, it's well defined for all those cases. The answers do not change if memcpy is replaced by copy assignment (copying for trivially copyable types is byte-wise copy. That's what makes it trivial), and for all trivially copyable types, move construction IS copy construction, so there's no difference there either.

From [basic.types]:

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

and:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1. [ Example:

T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p

—end example ]

where:

Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_- t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types. Scalar types, POD classes (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called POD types. Cv-unqualified scalar types, trivially copyable class types (Clause 9), arrays of such types, and nonvolatile const-qualified versions of these types (3.9.3) are collectively called trivially copyable types.

Where, from [class]:

A trivially copyable class is a class that:
(6.1) — has no non-trivial copy constructors (12.8),
(6.2) — has no non-trivial move constructors (12.8),
(6.3) — has no non-trivial copy assignment operators (13.5.3, 12.8),
(6.4) — has no non-trivial move assignment operators (13.5.3, 12.8), and
(6.5) — has a trivial destructor (12.4).

So, char, int, and struct { int data; }; are all trivially copyable types. The former two are cv-unqualified scalar types and the latter is a trivially copyable class type.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    From the quote: "Provided that t2p points to an initialized object". This condition is not met here. – Ben Voigt Oct 07 '15 at 16:39
  • @BenVoigt that doesn't mean that the `memcpy` provokes UB, though, does it? – davmac Oct 07 '15 at 16:41
  • 1
    That proves that the `memcpy` case is defined. Where is defined that *trivial copy assignment* is the same as *memcpy* (and particularly does not access the object's value)? – precarious Oct 07 '15 at 16:42
  • @precarious: I don't think this text actually proves that `memcpy` is ok. Your second point is definitely an issue (and I believe Barry's answer has gotten it wrong) – Ben Voigt Oct 07 '15 at 16:45
  • @BenVoigt Examples are non-normative, and pretty sure that text is there to imply that the pointers are non-null. If the source type is a default-initialized trivial type, then it has some unspecified value, so copying it will also have the same unspecified value. – Barry Oct 07 '15 at 16:50
  • 1
    @Barry: The standard is pretty clear on "no initialization is performed". I don't think it's fair to argue that creates an "initialized object". – Ben Voigt Oct 07 '15 at 16:51
  • @BenVoigt Nowhere am I requiring or arguing for the existence of an "initialized object." – Barry Oct 07 '15 at 16:57
  • @Barry copying via assignment, though, requires the use of an expression yielding the indeterminate value - and that provokes undefined behaviour. – davmac Oct 07 '15 at 16:59
  • @davmac: Except for "unsigned narrow character types" (`unsigned char`, possibly also `char`) – Ben Voigt Oct 07 '15 at 17:00
  • @BenVoigt right, but the question also asks about `int` and a struct type. – davmac Oct 07 '15 at 17:01
  • @BenVoigt Added another relevant quote. The exception applies here - because we're just byte-wise copying. – Barry Oct 07 '15 at 17:06
  • Can you prove the "*just*" in front of "byte-wise copying"? – precarious Oct 07 '15 at 17:12
  • 1
    I agree with Barry's latest edit, because the same paragraph that says "whether or not the object holds a valid value of type `T`, the underlying bytes can be copied" uses `memcpy` for its example. However I still disagree with "The answers do not change if memcpy is replaced by copy assignment" – Ben Voigt Oct 07 '15 at 17:22
  • @Barry you may want to see my [comment above](http://stackoverflow.com/questions/32997185/is-copying-trivially-copyable-objects-always-defined-in-c14#comment53827276_32997185) – Shafik Yaghmour Oct 07 '15 at 19:54