Using C standard libarary functions `std::memset`, `std::memcmp` or `std::memcpy` on non-trival C++ typesCXX-W2021
There are potential problems that can arise when initializing and comparing
objects of nontrivial class* types using std::memset()
, std::memcpy()
, and
std::memcmp()
. These functions operate directly on the memory and may
overwrite the value representation of an object into an unexpected state. Or
perform byte compare over instances of classes instead of using the comparator.
When an object is initialized, its object representation must be set up correctly for its type. For narrow types like integers or floating-point numbers, this generally involves setting the appropriate bits, aka just the value representation.
For nontrivial class types, there may be additional initialization steps
required to set up the object's state. Using std::memset()
to initialize
such an object will only perform value representation initialization and
can result in a partially-initialized or uninitialized object. Similarly,
std::memcpy
only performs a copy of value representation, which when
copying an object of the non-trivial type is insufficient.
Finally, when comparing objects of non-standard-layout class type, the value
representation of the objects may not be properly compared by std::memcmp()
or
related functions. This can lead to incorrect results or undefined behavior.
To avoid these issues, instead, use alternative methods that properly initialize and compare the objects.
C Library Function | C++ Alternative |
---|---|
std::memset | Class constructor |
std::memcpy , std::memmove , std::strcpy | Class constructor or operator=() |
std::memcmp , std::stdcmp | operator<() , operator>() , operator ==() or operator!=() |
Bad practice
class NonTrival {
int m1, m2;
int *ptr;
public:
NonTrival(): m1(10), m2(10), ptr(nullptr) {}
NonTrival(const NonTrival& other) {
if (this != &other) {
m1 = other.m1;
m2 = other.m2;
if (ptr) free(ptr);
ptr = new int;
*ptr = *other.ptr;
}
}
bool operator==(const NonTrival& other) {
// `ptr` is intensinonally ignored here
return m1 == other.m1 && m2 == other.m2;
}
reset() {
m1 = 10;
m2 = 10;
if (!ptr) free(ptr);
ptr = nullptr;
}
};
void reset(NonTrival& obj) {
// Problem: setting bytes of `obj` to zero using memset instead of `reset`
std::memset(obj, 0, sizeof(NonTrival));
}
void copy(NonTrival& src, const NonTrival& dest) {
// Problem: copying bytes using memcpy instead of using copy constructor
std::memcpy(src, dest, sizeof(NonTrival));
}
bool equals(const NonTrival& obj1, const NonTrival& obj2) {
// Problem: comparing bytes of `obj` using memcmp instead of using `operator=`
return std::memcmp(obj1, obj2, sizeof(NonTrival) == 0? true : false;
}
Recommended
class NonTrival {
int m1, m2;
int *ptr;
public:
NonTrival(): m1(10), m2(10), ptr(nullptr) {}
NonTrival(const NonTrival& other) {
if (this != &other) {
m1 = other.m1;
m2 = other.m2;
if (ptr) free(ptr);
ptr = new int;
*ptr = *other.ptr;
}
}
bool operator==(const NonTrival& other) {
// `ptr` is intensinonally ignored here
return m1 == other.m1 && m2 == other.m2;
}
reset() {
m1 = 10;
m2 = 10;
if (!ptr) free(ptr);
ptr = nullptr;
}
};
void reset(NonTrival& obj) { obj.reset(); }
void copy(NonTrival& src, const NonTrival& dest) { dest = src; }
bool equals(const NonTrival& obj1, const NonTrival& obj2) { return obj1 == obj2; }
References
- See "Trival class" under section "Properties of classes" here