Подтвердить что ты не робот

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.

4b9b3361

Ответ 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 генерирует точно такой же код в любом случае.