Если у меня есть такой объект:
struct {
uint32_t n;
uint8_t c;
} blob {};
тогда будет 3 дополненных байта.
Это UB для доступа к дополненным байтам? Например:
uint8_t * data = reinterpret_cast<uint8_t*>(&blob);
std::cout << data[4] << data[5] << data[6] << data[7];
Сначала я предположил, что это, вероятно, UB, но если это правда, то memcpy также будет UB:
memcpy(buf, &blob, sizeof(blob));
Мои конкретные вопросы:
Нет, это не UB для доступа к заполнению, когда весь объект был инициализирован нулем (стандарт говорит в §8.5 / 5, что заполнение инициализируется в 0 битов, когда объекты инициализируются нулями) или инициализируется значением, и это не класс с определяемым пользователем конструктором.
Я думаю, при правильных обстоятельствах, вы могли бы в конечном итоге с UB для этого. Я думаю о том, где у вас есть память с проверкой ECC или Parity, где биты ecc / parity устанавливаются путем записи в память. Если блок памяти ранее не использовался [никогда не записывался в ВСЕ], и вы читали неинициализированные байты в поле заполнения, это может привести к ошибке ecc / parity, когда память, которая никогда не была записана читается
Конечно, в такой системе вы могли бы избежать целой кучи боли, просто выполняя «заполнение всей памяти» в какой-то момент во время загрузки, так как это было бы просто так:
struct Blob
{
uint32_t n;
uint8_t c;
};
Blob *b = malloc(sizeof(Blob)*10);
for(int i = 0; i < 10; i++)
{
b[i].n = i;
b[i].c = i;
}...
Blob a[3];
memcpy(a, &b[1], sizeof(a)); // Copies 3 * Blob objects, including padding.
Теперь, поскольку не все биты b [x] были установлены, возможно, не удастся скопировать данные в memcpy из-за ошибок четности / ecc. Это было бы очень плохо. Но в то же время компилятор нельзя принудительно «устанавливать» все области заполнения.
Я пришел к выводу, что это UB, но вряд ли это вызовет проблемы, если нет особых обстоятельств. Конечно, вы увидите вышеупомянутый тип memcpy
код в большом количестве кода.
Если это не неопределенное поведение, оно определенно зависит от реализации. В то время как стандарт C ++ не гарантирует многое о том, что делает ваша программа, спецификация ABI вашей системы — SysV если вы используете Linux — будет. Я подозреваю, что если вы возитесь с битами заполнения, вы, вероятно, больше интересуетесь тем, как ваша программа будет вести себя в вашей системе, чем тем, как она будет вести себя в любой произвольной C ++ — соответствующей системе.
Структура POD будет жить в непрерывном блоке памяти размером не менее sizeof (struct) байтов (который включает в себя любые байты заполнения). Доступ к дополнительным байтам (если они существуют) будет UB, только если он не был инициализирован.
memset(&s, 0, sizeof(s));
Это инициализирует все байты, включая заполнение. После чего чтение с отступа не будет UB.
Конечно, memset()
такое C-ism, и мы бы никогда не сделали этого на C ++, верно?
В Си это не неопределенное поведение. Единственный раз, когда вы получаете неопределенное поведение при доступе к неинициализированным вещам (таким как заполнение в объектах), это когда у объекта есть автоматическая продолжительность хранения и НИКОГДА НЕ БЫЛ ЕГО АДРЕС:
6.3.2.1.2:
Если
lvalue обозначает объект автоматической длительности хранения, который мог быть
объявлено с классом хранения регистра (никогда не был взят его адрес), и этот объект
неинициализирован (не объявлен инициализатором, и присвоение ему не было
выполняется до использования), поведение не определено.
Но в этом случае вы берете адрес (с &
), поэтому поведение четко определено (ошибки не возникает), но вы можете получить любое случайное значение.
В C ++ все ставки отключены, как это обычно бывает.