C для создания битовой маски - возможно? И я нашел ошибку GCC?
Мне немного любопытно создать макрос для создания битовой маски для регистра устройств, до 64 бит. Таким образом, BIT_MASK(31)
создает 0xffffffff
.
Однако несколько примеров C не работают так, как я думал, поскольку вместо этого я получаю 0x7fffffff
. Это так: если компилятор предполагает, что я хочу иметь подписанный вывод, а не без знака. Поэтому я попробовал 32
и заметил, что это значение возвращается к 0. Это из-за стандартов C, заявляющих, что если значение сдвига больше или равно количеству бит в переносимом операнде, тогда результат undefined. Это имеет смысл.
Но, учитывая следующую программу, 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 оптимизирует что-то неправильно ИЛИ undefined природа 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
.
Ответы
Ответ 1
Вот версия макроса, которая будет работать для произвольных положительных входов. (Отрицательные входы по-прежнему вызывают поведение undefined...)
#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;
}
Как отмечалось в Mystical, смещение более 32 бит с 32-разрядным целым приводит к реализации undefined. Вот три разных варианта смещения:
- На x86 проверяйте только 5 битов суммы сдвига, поэтому
x << 32 == x
.
- В PowerPC изучите только 6 бит величины сдвига, поэтому
x << 32 == 0
, но x << 64 == x
.
- В Cell 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))
Ответ 2
Смещение более или равным размеру целочисленного типа - undefined поведение.
Так что нет, это не ошибка GCC.
В этом случае литерал 1
имеет тип int
, который 32 бита в обеих используемых вами системах. Таким образом, сдвиг на 32 вызовет поведение undefined.
В первом случае компилятор не может разрешить величину сдвига до 32. Поэтому он, скорее всего, просто выдает нормальную инструкцию shift. (который в x86 использует только нижние 5-бит). Таким образом, вы получаете:
(unsigned int)(1 << 0) - 1
которое равно нулю.
Во втором случае GCC может разрешить величину сдвига до 32. Поскольку это undefined поведение, он (по-видимому) просто заменяет весь результат на 0:
(unsigned int)(0) - 1
чтобы получить ffffffff
.
Итак, это случай, когда GCC использует поведение undefined как возможность оптимизировать.
(Хотя лично я бы предпочел, чтобы оно выдавало предупреждение вместо этого.)
Связано: Почему целостное переполнение на x86 с GCC вызывает бесконечный цикл?
Ответ 3
Предполагая, что у вас есть рабочая маска для бит 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)
Ответ 4
Как насчет:
#define BIT_MASK(n) (~(((~0ULL) >> (n)) << (n)))
Это работает на всех системах с прямым порядком байтов, выполнение -1 для инвертирования всех битов не работает на системе с прямым порядком байтов.
Ответ 5
#define BIT_MASK(foo) ((~ 0ULL) >> (64-foo))
Я немного параноик об этом. Я думаю, это предполагает, что unsigned long long
составляет ровно 64 бита. Но это запуск, и он работает до 64 бит.
Возможно, это правильно:
define BIT_MASK(foo) ((~ 0ULL) >> (sizeof(0ULL)*8-foo))
Ответ 6
Так как вам нужно избегать переключения на столько битов, сколько есть в типе (будь то 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>
.
Ответ 7
"Традиционная" формула (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 (результат будет специфичным для компилятора/процессора), но это редкая возможность для всех видов использования, которые у меня есть (*), и их можно решить, добавив защитный оператор "if" только там, где это необходимо (и даже лучше "утверждать", чтобы проверить как верхнюю, так и нижнюю границы).
(*) - обычно данные поступают из файла или канала, а размер - в байтах. Если размер равен нулю, тогда нет данных, поэтому код ничего не должен делать.
Ответ 8
Ответ @iva2k позволяет избежать разветвления и является правильным, если длина равна 64 бит. Работая над этим, вы также можете сделать это:
#define BIT_MASK(length) ~(((unsigned long long) -2) << length - 1);
gcc генерирует точно такой же код в любом случае.