Битовые поля и точки последовательности

Для реализации, которая упаковывает f0 и f1 в один и тот же байт, определена ли программа ниже?

struct S0 {
       unsigned f0:4;
       signed f1:4;
} l_62;

int main (void) {
       (l_62.f0 = 0) + (l_62.f1 = 0);
       return 0;
}

Меня интересует ответ для С99 и для С11 если есть основания думать что там по другому.

В C99 все, что я нашел, было 6,5: 2:

Между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза при вычислении выражения. [...]

Мне непонятно, какие последствия этот абзац имеет для программы выше.

Судя по большому количеству рандомизированных тестов, большинство компиляторов генерируют код, в котором два назначения не мешают друг другу.


person Pascal Cuoq    schedule 05.02.2012    source источник
comment
Эти части стандартов вызывают у меня головную боль. Мое текущее чтение состоит в том, что намерение состоит в том, чтобы это было UB (например, в C11 ясно, что изменение двух полей в двух потоках не синхронизируется), но язык в 6.5 забыл упомянуть битовое поле, как в других места, где битовые поля имеют специальную обработку.   -  person AProgrammer    schedule 05.02.2012
comment
@AProgrammer: приведенный выше код имеет ясное логическое значение (обрабатывайте записи так, как если бы они происходили последовательно, в любом порядке), и нет причин, по которым компилятор, автор которого не является тупым, не должен генерировать код, который создает такое поведение в однопоточный случай. Можете ли вы предложить какую-либо правдоподобную причину, по которой авторы Стандарта хотели, чтобы он был UB?   -  person supercat    schedule 02.11.2017


Ответы (2)


C11 считает соседние битовые поля named частью одной и той же ячейки памяти. Не гарантируется атомарное обновление таких битовых полей, другими словами, если одно обновление не выполняется явно перед другим, поведение не определено. 3.14 memory location также содержит подробное объяснение того, когда два поля можно считать находящимися в разных местах памяти, поэтому их обновления можно рассматривать независимо.

Если бы вы изменили свою структуру

struct S0 {
       unsigned f0:4;
       int :0;
       signed f1:4;
} l_62;

так что между двумя битовыми полями есть этот причудливый «разделитель местоположения памяти», ваш код гарантированно будет в порядке.

Для С99 дело похоже сложнее, нет такого подробного понятия расположения памяти. В недавнем обсуждении в списке рассылки ядра linux было утверждение, что вообще для всех пар битовых полей будет гарантия атомарности при обновлении любого из них. Отправной точкой этого обсуждения был случай, когда gcc неожиданно загрязнил небитовое поле, соседнее с битовым полем, что привело к ложным сбоям.

person Jens Gustedt    schedule 05.02.2012
comment
Изменение одного и того же места в памяти — это гонка данных в C11, это точно. Но в то время как 5.1.2.4/4 говорит о расположении памяти, 6.5/2 говорит только о скалярных объектах. И я не уверен, что это оплошность. Соответствующие формулировки в C++ аналогичны, гонка данных, но, по-видимому, не UB для одного выражения в одном потоке. - person AProgrammer; 06.02.2012
comment
@AProgrammer: на большинстве платформ было бы крайне непрактично предписывать, чтобы одновременная (разные потоки) запись в разные битовые поля в одном и том же элементе хранилища вел себя последовательно-согласованным образом. Большинство платформ могли бы, практически без затрат, гарантировать, что запись в одно поле не повлияет на одновременное чтение другого, но поскольку это не будет бесплатным на всех платформах, Стандарт не требует этого. Однако я не вижу ничего, что указывало бы на то, что компиляторы не должны обрабатывать однопоточные случаи. - person supercat; 02.11.2017

Присваивание здесь относится к членам структуры. Тот факт, что они используют одно и то же хранилище, не должен влиять на логику. На самом деле вы на самом деле не сделали присвоение одной и той же вещи.

Я, конечно, не языковой юрист.

person ams    schedule 05.02.2012