C макросом для создания битовой маски - возможно? И я нашел ошибку GCC? - PullRequest
14 голосов
/ 08 января 2012

Мне немного любопытно создать макрос для генерации битовой маски для регистра устройства, до 64 бит. Так, что BIT_MASK(31) производит 0xffffffff.

Тем не менее, несколько примеров C не работают, как я думал, вместо этого я получаю 0x7fffffff. Это как если компилятор предполагает, что я хочу подписанный вывод, а не беззнаковый. Итак, я попытался 32 и заметил, что значение возвращается к 0. Это связано со стандартами C, утверждающими, что если значение сдвига больше или равно количеству битов в операнде, подлежащем сдвигу, то результат не определено Это имеет смысл.

Но, учитывая следующую программу, bits2.c:

#include <stdio.h>

#define BIT_MASK(foo) ((unsigned int)(1 << foo) - 1)

int main()
{
    unsigned int foo;
    char *s = "32";

    foo = atoi(s);
    printf("%d %.8x\n", foo, BIT_MASK(foo));

    foo = 32;
    printf("%d %.8x\n", foo, BIT_MASK(foo));

    return (0);
}


Если я скомпилирую с gcc -O2 bits2.c -o bits2 и запусту его на компьютере с Linux / x86_64, я получу следующее:

32 00000000
32 ffffffff


Если я возьму тот же код и скомпилирую его на компьютере с Linux / MIPS (big-endian), я получу следующее:

32 00000000
32 00000000


На компьютере x86_64, если я использую gcc -O0 bits2.c -o bits2, я получаю:

32 00000000
32 00000000


Если я настрою BIT_MASK на ((unsigned int)(1UL << foo) - 1), то результат будет 32 00000000 для обеих форм, независимо от уровня оптимизации gcc.

Похоже, что в x86_64 gcc оптимизирует что-то неправильно ИЛИ неопределенная природа сдвига влево 32-битного 32-битного числа определяется аппаратным обеспечением каждой платформы.




Учитывая все вышесказанное, возможно ли программно создать макрос C, который создает битовую маску из одного бита или диапазона битов?

т.е:.

BIT_MASK(6) = 0x40
BIT_FIELD_MASK(8, 12) = 0x1f00

Предположим, что BIT_MASK и BIT_FIELD_MASK работают с 0-индекса (0-31). BIT_FIELD_MASK - создать маску из битового диапазона, то есть 8:12.

Ответы [ 8 ]

12 голосов
/ 08 января 2012

Вот версия макроса, которая будет работать для произвольных положительных входных данных.(Отрицательные входные данные по-прежнему вызывают неопределенное поведение ...)

#include <limits.h>
/* A mask with x least-significant bits set, possibly 0 or >=32 */
#define BIT_MASK(x) \
    (((x) >= sizeof(unsigned) * CHAR_BIT) ?
        (unsigned) -1 : (1U << (x)) - 1)

Конечно, это несколько опасный макрос, поскольку он оценивает свой аргумент дважды.Это хорошая возможность использовать static inline, если вы используете GCC или целевой C99 в целом.

static inline unsigned bit_mask(int x)
{
    return (x >= sizeof(unsigned) * CHAR_BIT) ?
        (unsigned) -1 : (1U << x) - 1;
}

Как отметил Мистик, смещение более 32 бит с 32-битным целым числом приводит к определенный реализацией неопределенное поведение.Вот три различных реализации сдвига:

  • На x86, проверять только 5 младших битов величины сдвига, поэтому x << 32 == x.
  • На PowerPC только проверять 6 младшихбиты величины сдвига, поэтому x << 32 == 0, но x << 64 == x.
  • В SPU ячейки проверьте все биты, поэтому x << y == 0 для всех y >= 32.

Однако,компиляторы могут делать все, что им захочется, если вы сдвигаете 32-битный операнд на 32 или более бит, и они даже могут вести себя непоследовательно (или заставлять демонов вылетать из носа).

Реализация BIT_FIELD_MASK:

Устанавливает биты от a до b (включительно) до 0 <= a <= 31 и 0 <= b <= 31.

#define BIT_MASK(a, b) (((unsigned) -1 >> (31 - (b))) & ~((1U << (a)) - 1))
.
8 голосов
/ 08 января 2012

Сдвиг больше или равен размеру целочисленного типа: неопределенное поведение .
Так что нет, это не ошибка GCC.

В этом случае литерал 1 имеет тип int, который является 32-разрядным в обеих системах, которые вы использовали. Таким образом, сдвиг на 32 вызовет это неопределенное поведение.


В первом случае компилятор не может разрешить значение сдвига до 32. Поэтому он, скорее всего, просто выдает обычную инструкцию сдвига. (который в x86 использует только нижние 5-битные) Итак, вы получите:

(unsigned int)(1 << 0) - 1

который равен нулю.

Во втором случае GCC может разрешить величину сдвига до 32. Поскольку это неопределенное поведение , он (по-видимому) просто заменяет весь результат на 0:

(unsigned int)(0) - 1

так вы получите ffffffff.


Так что это тот случай, когда GCC использует неопределенное поведение как возможность для оптимизации.
(Хотя лично я предпочел бы, чтобы вместо этого было выдано предупреждение.)

Related: Почему целочисленное переполнение в x86 с GCC вызывает бесконечный цикл?

2 голосов
/ 08 января 2012

Если у вас есть рабочая маска для n битов, например,

// set the first n bits to 1, rest to 0
#define BITMASK1(n) ((1ULL << (n)) - 1ULL)

Вы можете сделать битовую маску диапазона, снова сдвинув:

// set bits [k+1, n] to 1, rest to 0
#define BITNASK(n, k) ((BITMASK(n) >> k) << k)

Тип результата unsigned long long int в любом случае.

Как уже говорилось, BITMASK1 - это UB, если n не мало. Общая версия требует условного выражения и дважды оценивает аргумент:

#define BITMASK1(n) (((n) < sizeof(1ULL) * CHAR_BIT ? (1ULL << (n)) : 0) - 1ULL)
1 голос
/ 20 июня 2013

Как насчет:

#define BIT_MASK(n) (~(((~0ULL) >> (n)) << (n)))

Это работает во всех системах с прямым порядком байтов, выполнение -1 для инвертирования всех битов не работает в системе с прямым порядком байтов.

0 голосов
/ 22 июня 2017

@ Ответ iva2k избегает ветвления и является правильным, когда длина составляет 64 бита. Работая над этим, вы также можете сделать это:

#define BIT_MASK(length) ~(((unsigned long long) -2) << length - 1);

gcc все равно сгенерирует точно такой же код.

0 голосов
/ 09 сентября 2014

«Традиционная» формула (1ul<<n)-1 ведет себя по-разному на разных компиляторах / процессорах при n = 8 * sizeof (1ul). Чаще всего он переполняется при n = 32. Любые добавленные условия будут оцениваться n раз. Переход на 64-битный (1ull<<n)-1 вариант, но проблема переносится в n = 64.

Моя формула перехода:

#define BIT_MASK(n) (~( ((~0ull) << ((n)-1)) << 1 ))

Он не переполняется при n = 64 и оценивает n только один раз.

В качестве обратной стороны он будет компилироваться в 2 инструкции LSH, если n является переменной. Также n не может быть 0 (результат будет зависеть от компилятора / процессора), но это редкая возможность для всех применений, которые у меня есть (*), и с ними можно бороться, добавляя защитное выражение «если» только там, где это необходимо (и даже лучше "утверждать", чтобы проверить верхнюю и нижнюю границы).


(*) - обычно данные поступают из файла или канала и имеют размер в байтах. Если размер равен нулю, то данных нет, поэтому код все равно ничего не должен делать.

0 голосов
/ 08 января 2012

Поскольку вам нужно избегать смещения на столько бит, сколько имеется в типе (будь то unsigned long или unsigned long long), вы должны быть более хитрыми в маскировании при работе с полной шириной типа. Один из способов - подкрасться к нему:

#define BIT_MASK(n) (((n) == CHAR_BIT * sizeof(unsigned long long)) ? \
                         ((((1ULL << (n-1)) - 1) << 1) | 1) : \
                           ((1ULL << (n  )) - 1))

Для константы n, такой как 64, компилятор оценивает выражение и генерирует только тот случай, который используется. Для переменной времени выполнения n это происходит так же плохо, как и раньше, если n больше, чем число бит в unsigned long long (или отрицательно), но работает нормально без переполнения для значений n в диапазоне 0..(CHAR_BIT * sizeof(unsigned long long)).

Обратите внимание, что CHAR_BIT определено в <limits.h>.

0 голосов
/ 08 января 2012
#define BIT_MASK(foo) ((~ 0ULL) >> (64-foo))

Я немного параноик по этому поводу. Я думаю, что это предполагает, что unsigned long long является точно 64 бит. Но это начало, и оно работает до 64 бит.

Может быть, это правильно:

define BIT_MASK(foo) ((~ 0ULL) >> (sizeof(0ULL)*8-foo))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...