Что касается маскировки бит в C. Почему (~ (~ 0 << N)) предпочтительнее, чем ((1 << N) -1)?

Я знаю, что ~ 0 будет оценивать максимальный бит размером 1 бит (и, таким образом, берет на себя переносимость), но я все еще не понимаю, почему ((1 < N) - 1) не рекомендуется?

Пожалуйста, поделитесь, если вы использовали вторую форму и попали в беду.

Ответ 1

Посмотрите на эти строки:

1. printf("%X", ~(~0 << 31) );
2. printf("%X", (1 << 31) - 1 );

Линия 1 компилируется и ведет себя как ожидалось.

Строка 2 выводит выражение integer предупреждения в выражении.

Это связано с тем, что 1 << 31 обрабатывается по умолчанию как подписанный int, поэтому 1 << 31 = -2147483648, который является наименьшим возможным целым числом.

В результате покоя 1 вызывает переполнение.

Ответ 2

Первая форма, безусловно, не является предпочтительной, и я бы зашел так далеко, чтобы сказать, что она должна использоваться никогда. В одной системе дополнений, которая не поддерживает отрицательный нуль, ~0 вполне может быть представлением ловушки и, таким образом, вызывать UB при использовании.

С другой стороны, 1<<31 также является UB, если int является 32-битным, так как он переполняется.

Если вы действительно имеете в виду 31 как константу, 0x7fffffff - это самый простой и правильный способ написания вашей маски. Если вам нужен только бит знака int, INT_MAX - это самый простой и правильный способ написания вашей маски.

Пока вы знаете, что битдвиг не будет переполняться, (1<<n)-1 - это правильный способ сделать маску с самым низким битом n. Может быть предпочтительнее использовать (1ULL<<n)-1, за которым следует приведение или неявное преобразование, чтобы не беспокоиться о проблемах с подписью и переполнении в сдвиге.

Но что бы вы ни делали, не используйте оператор ~ со знаками целых чисел. Когда-нибудь.

Ответ 3

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

Поэтому я всегда буду делать что-то вроде

-UINT32_C(1)
~UINT32_C(0)

которые полностью эквивалентны, и в конце это просто используется для использования UINT32_MAX и Co.

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

(UINT32_C(1) << N) - UINT32_C(1)

Ответ 4

Я бы не предпочел один к другому, но я видел много ошибок с (1<<N), где значение должно было быть 64-битным, но "1" было 32-битным (ints были 32-разрядными), и результат был неверно при N >= 31. 1ULL вместо 1 исправит это. Эта опасность таких сдвигов.

Кроме того, сдвиги int с помощью CHAR_BIT * sizeof (int) или более позиций (аналогично длинным (часто 64-разрядным)) с помощью CHAR_BIT * sizeof (long long) или более позиций) не определены. Из-за этого может быть безопаснее смещаться так: ~0u>>(CHAR_BIT*sizeof(int)-N), но в этом случае N не может быть 0.

Ответ 5

EDIT: исправлена ​​глупая ошибка; и отметил возможные проблемы с переполнением.

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

Другие ответы отметили возможность переполнения во второй форме.

Я вижу мало выбора между ними.

Ответ 6

Почему обескураженный
~ 0 - операция с одним циклом и, следовательно, быстрее ((1 сначала выполняет сдвиг, а затем вычитание, которое является арифметической операцией, поэтому из-за вычитания он будет потреблять много циклов и, следовательно, лишние накладные расходы.

Подробнее
более того, когда вы делаете ((1 < N) -1) или ((M < N) -1) является одинаковым, предполагая, что N относится к размеру M в битах, поскольку он будет смывать все биты. здесь 1 является целым числом, обычно 32 бит на почти всех существующих платформах 32/64 бит, поэтому N можно считать 32.

Результат будет, однако, не одинаковым, если вы начинаете от 1 до длинного и делаете (((long) 1 < 32) -1). здесь вам нужно использовать 64 вместо 32, 64 - размер длинного бита.