Как компилятор реализует арифметику битового поля?

Когда вы задаете вопрос о том, как сделать завершенное вычитание из разряда N бит, я получил следующий ответ:

template<int bits>
int
sub_wrap( int v, int s )
{
    struct Bits { signed int r: bits; } tmp;
    tmp.r = v - s;
    return tmp.r;
}

Это аккуратно и все, но как это реализовать компилятор? Из этот вопрос. Я понимаю, что доступ к битовым полям более или менее таков, что делать это вручную, но как насчет того, чтобы в сочетании с арифметикой, как в этом примере? Будет ли это так же быстро, как хороший ручной подход к бит-кручению?

Ответ для "gcc" в роли "компилятора" был бы замечательным, если кто-то захочет получить конкретную информацию. Я пробовал читать сгенерированную сборку, но в настоящее время это выходит за рамки меня.

Ответы

Ответ 1

Как написано в другом вопросе, беззнаковая математика обертывания может быть выполнена как:

int tmp = (a - b) & 0xFFF;  /* 12 bit mask.  */

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

Для чтения вам нужно сделать что-то немного другое.

Для неподписанных математик достаточно сделать это:

int result = tmp;  /* whatever bit count, we know tmp contains nothing else.  */

или

int result = tmp & 0xFFF;  /* 12bit, again, if we have other junk in tmp.  */

Для подписанной математики дополнительная магия является расширением знака:

int result = (tmp << (32-12)) >> (32-12); /* asssuming 32bit int, and 12bit value. */

Все, что делает, реплицирует верхний бит битового поля (бит 11) через более широкий int.

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

(Я не читал стандарт, но я подозреваю, что полагаться на бит-поля для правильной работы при переполнении может быть небезопасным?)

Ответ 2

У компилятора есть знания о размере и точной позиции r в вашем примере. Предположим, что это похоже на

[xxxxrrrr]

Тогда

tmp.r = X;

может, например, (b-суффикс, указывающий бинарные литералы, & побитовым и | побитовым или)

tmp = (tmp & 11110000b)   // <-- get the remainder which is not tmp.r
    | (X   & 00001111b);  // <-- put X into tmp.r and filter away unwanted bits

Представьте, что ваш макет

[xxrrrrxx]   // 4 bits, 2 left-shifts

расширение может быть

tmp = (tmp    & 11000011b)   // <-- get the remainder which is not tmp.r
    | ((X<<2) & 00111100b);  // <-- filter 4 relevant bits, then shift left 2

Как выглядит X, будь то сложная формулировка или просто литерал, на самом деле не имеет значения.

Если ваша архитектура не поддерживает такие побитовые операции, все еще есть умножения и деления по мощности двух, чтобы имитировать смещение, и, вероятно, они также могут использоваться для фильтрации нежелательных бит.