What governs that std::memset may be used if the destination object is trivially copyable and is not a potentially-overlapping subobject?

Viewed 113

Cppreference's page on std::memset states:

std::memset

// Defined in header <cstring>
void* memset( void* dest, int ch, std::size_t count );

[...] If the object is a potentially-overlapping subobject or is not TriviallyCopyable (e.g., scalar, C-compatible struct, or an array of trivially copyable type), the behavior is undefined.

What rules in the standard supports this claim?

1 Answers

(Self-answered as I think I found the complete answer during the progress of posting the question)


[cstring.syn] Covers that we need to turn to the C standard library header string.h for the meaning of std::memset:

namespace std {
  // ...
  void* memset(void* s, int c, size_t n);
  // ...
}

/1 The contents and meaning of the header are the same as the C standard library header <string.h>. [...]

E.g. the C11 draft via N1570 specifies that the C standard library memset copies a byte (unsigned char) into the first n bytes of the destination object [emphasis mine]:

7.24.6.1 The memset function

/1 Synopsis

#include <string.h>
void *memset(void *s, int c, size_t n);

/2 Description

The memset function copies the value of c (converted to an unsigned char) into each of the first n characters of the object pointed to by s.

/3 Returns

The memset function returns the value of s.

Since this is the C standard "object" does not mean the same things as in C++; Section 3.15p1:

object region of data storage in the execution environment, the contents of which can represent values

With this in mind, we go back to the C++ standard and [basic.types]/4, which tells us that the object representation of a (C++) object is the sequence of underlying unsigned char objects, and that for trivially copyable types, particularly, the value representation of the object is part of the object representation:

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). [...] For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values42.

with the footnote 42 clarifying the intent of this memory model representation allowing compatibility when interfacing with the C:

The intent is that the memory model of C++ is compatible with that of ISO/IEC 9899 Programming Language C.

[basic.types]/3 expands on the effect of byte-wise copy of the underlying bytes from a source object to destination object (both of trivially copyable type), with an additional restriction that neither the source nor the destination object of such a byte-copy operation shall be a potentially-overlapping subobject:

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 potentially-overlapping subobject, if the underlying bytes ([intro.memory]) making up obj1 are copied into obj2,41 obj2 shall subsequently hold the same value as obj1.

From this this is holds that std::memset may be used to set all (n == sizeof(T)) or a sub-set of (n < sizeof(T)) of the unsigned char objects in the underlying object representation of an object whose type is trivially copyable and where the object is not a potentially-overlapping subobject. The resulting value representation of the object is a discrete element of an implementation-defined set of values that the particular object represents. Note however that reading these values is not necessarily well-defined, one notorious example being using std::memset to set all the bits of a floating point to zero in a non-IEEE754-adhering implementation where "all bits zero" could represent a trap. Another thing to consider when using std::memset is that whilst it may create formally well-defined objects (in the standardese sense) their resulting values may violate class invariants.

Related