Не читайте padding-биты объекта

Не обращайтесь к битам представления объекта, которые не входят в представление его значения. Или, другими словами, не работайте с объектом как с сырыми байтами (memcmp, memset, хеширование, сериализация), если не все биты его представления участвуют в представлении значения. Сравнивайте и копируйте поля, а не байты.

Представлением объекта типа T является последовательность N байтов (unsigned char) в памяти, где N = sizeof(T). Представлением значения объекта T является является множество битов, в которых хранится значение типа T.

Проблема может заключаться в заполняющих (padding) битах, содержимое которых может быть неопределено.

Неправильно:

#include <cstring>

struct S {
    char c;
    // padding в 3 байта
    int i;
};

bool equal(const S& a, const S& b) {
    return std::memcmp(&a, &b, sizeof(S)) == 0;
}

Правильно:

bool equal(const S& a, const S& b) {
    return a.c == b.c && a.i == b.i;
}

В С++20:

struct S {
    char c;
    int i;
    bool operator==(const S&) const = default;
};

Та же проблема возникает с неиспользуемыми битами в блоке размещения битовых полей.

Неправильно:

#include <cstring>

struct Flags {
    unsigned a : 3;
    unsigned b : 3;
    // остальные биты единицы размещения не входят в представление значения
};

bool equal(const Flags& x, const Flags& y) {
    return std::memcmp(&x, &y, sizeof(Flags)) == 0;
}

Правильно:

bool equal(const Flags& x, const Flags& y) {
    return x.a == y.a && x.b == y.b;
}

У полиморфных классов в объекте есть vptr - указатель на таблицу виртуальных функций. Он входит в представление объекта, но не в представление значения: значение определяется значениями членов и динамическим типом. Поэтому важно правильно сравнивать/хешировать/сериализовать сырые байты.

Неправильно:

#include <cstring>

struct Base {
    virtual void f();
    int x;
};

bool equal(const Base& a, const Base& b) {
    return std::memcmp(&a, &b, sizeof(Base)) == 0;
}