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

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

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

Ответы [ 6 ]

10 голосов
/ 05 октября 2011

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

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

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

Строка 2 выдает предупреждение целочисленное переполнение в выражении .

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

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

4 голосов
/ 05 октября 2011

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

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

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

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

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

1 голос
/ 05 октября 2011

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

Так что я всегда буду делать что-то вроде

-UINT32_C(1)
~UINT32_C(0)

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

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

(UINT32_C(1) << N) - UINT32_C(1)
0 голосов
/ 05 октября 2011

Почему не рекомендуется
~ 0 - это операция с одним циклом и, следовательно, более быстрая ((1

0 голосов
/ 05 октября 2011

РЕДАКТИРОВАТЬ : исправлена ​​глупая ошибка;и отметил возможные проблемы переполнения.

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

В других ответах отмечалась возможность переполнения во второй форме.

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

0 голосов
/ 05 октября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...