Доступно ли UB для "заполненных" байтов?

Если у меня есть такой объект, как этот:

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 для доступа к заполненным байтам?
  • Если нет, значит ли это, что значения также определены?

Ответ 1

Нет, это не UB для доступа к заполнению, когда весь объект был инициализирован нулем (стандарт говорит в § 8.5/5, что заполнение инициализируется до 0 бит, когда объекты инициализируются нулем) или инициализируется инициализацией, и это не является классом с определяемым пользователем конструктором.

Ответ 2

Я думаю, что при правильных обстоятельствах вы могли бы получить UB для этого. Я думаю о том, где у вас есть память либо с проверкой ECC, либо с контролем четности, где бит ecc/четности задается путем записи в память. Если блок памяти не использовался до того, как [никогда не был записан в AT ALL], и вы читаете неинициализированные байты в поле заполнения, это может привести к ошибке 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, указанный выше, в большом количестве кода.

Ответ 3

В C это не поведение undefined. Единственный раз, когда вы получаете поведение undefined от доступа к неинициализированным материалам (например, отступы в объектах), - это когда объект имеет автоматическую продолжительность хранения и НИКОГДА НЕ ИМЕЕТ ЕГО АДРЕС:

6.3.2.1.2: Если lvalue обозначает объект с автоматическим временем хранения, который мог бы быть объявлен с классом хранения регистров (никогда не был принят его адрес), и этот объект не инициализируется (не объявляется с инициализатором, и никакое присвоение ему не было выполняется до использования), поведение undefined.

Но в этом случае вы берете адрес (с помощью &), поэтому поведение корректно определено (ошибка не возникает), но вы можете получить произвольное значение.

В С++ все ставки отключены, как это обычно бывает.

Ответ 4

Если это не поведение undefined, оно, безусловно, определено в реализации. Хотя стандарт С++ не гарантирует многого о том, что делает ваша программа, ваша спецификация системы ABI - SysV, если вы используете Linux - будет, Я подозреваю, что, если вы бряцаете в битах, вы, вероятно, больше заинтересованы в том, как ваша программа будет вести себя в вашей системе, чем в том, как она будет вести себя на любой произвольной С++-соответствующей системе.

Ответ 5

Структура POD будет жить в непрерывном блоке памяти по меньшей мере sizeof (struct) байтов (включая любые байты заполнения). Доступ к байтам заполнения (если они существуют) будет UB, только если он не был сначала инициализирован.

memset(&s, 0, sizeof(s));

Это инициализировало бы все байты, включая дополнение. После чего чтение из прокладки не будет UB.

Конечно, memset() является C-ism, и мы никогда не будем делать это на С++, правильно?