Безопасно ли использовать -1, чтобы установить все биты в true? - PullRequest
129 голосов
/ 01 мая 2009

Я видел, как этот шаблон часто используется в C & C ++.

unsigned int flags = -1;  // all bits are true

Это хороший переносной способ сделать это? Или лучше использовать 0xffffffff или ~0?

Ответы [ 20 ]

152 голосов
/ 01 мая 2009

Я рекомендую вам сделать это именно так, как вы показали, так как это самый прямой. Инициализируйте значение -1, которое будет работать всегда , независимо от фактического представления знака, в то время как ~ иногда будет иметь удивительное поведение, поскольку вам потребуется правильный тип операнда. Только тогда вы получите самое высокое значение типа unsigned.

В качестве примера возможного сюрприза рассмотрим следующий:

unsigned long a = ~0u;

Не обязательно хранить шаблон со всеми битами 1 в a. Но сначала он создаст шаблон со всеми битами 1 в unsigned int, а затем назначит его a. Что происходит, когда unsigned long имеет больше битов, так это то, что не все из них равны 1.

И рассмотрим этот, который потерпит неудачу при представлении дополнения к не-двум:

unsigned int a = ~0; // Should have done ~0u !

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

Вы должны понять, что все дело в ценностях, а не в битах. Переменная инициализируется значением . Если в инициализаторе вы измените биты переменной, используемой для инициализации, значение будет сгенерировано в соответствии с этими битами. Значение, которое вам нужно для инициализации a до максимально возможного значения, равно -1 или UINT_MAX. Второй будет зависеть от типа a - вам нужно будет использовать ULONG_MAX для unsigned long. Однако первое не будет зависеть от его типа, и это хороший способ получить самое высокое значение.

Мы не говорим о том, имеет ли -1 все биты один (не всегда). И мы не говорим о том, имеет ли ~0 все биты один (он, конечно, имеет).

Но мы говорим о том, каков результат инициализированной переменной flags. И для этого только -1 будет работать с каждым типом и машиной.

47 голосов
/ 01 мая 2009
  • unsigned int flags = -1; является портативным.
  • unsigned int flags = ~0; не переносимо, потому что полагается на представление с дополнением до двух.
  • unsigned int flags = 0xffffffff; не переносимо, потому что он предполагает 32-битные целые числа.

Если вы хотите установить все биты способом, гарантированным стандартом C, используйте первый.

24 голосов
/ 01 мая 2009

Честно говоря, я думаю, что все FFF более читабельны. Что касается комментария о том, что это антипаттерн, если вы действительно заботитесь о том, чтобы все биты были установлены / очищены, я бы сказал, что вы, вероятно, находитесь в ситуации, когда вам все равно важен размер переменной, что потребует чего-то вроде boost :: uint16_t и т. д.

17 голосов
/ 01 мая 2009

Чтобы избежать упомянутых проблем, достаточно просто:

unsigned int flags = 0;
flags = ~flags;

Переносной и в точку.

13 голосов
/ 01 мая 2009

Я не уверен, что использование unsigned int для флагов - хорошая идея, прежде всего, в C ++. А как насчет битсет и тому подобное?

std::numeric_limit<unsigned int>::max() лучше, потому что 0xffffffff предполагает, что unsigned int является 32-разрядным целым числом.

11 голосов
/ 01 июня 2011
unsigned int flags = -1;  // all bits are true

«Это хороший [,] портативный способ сделать это?»

Портативный? Да .

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

Кроме того, этот метод подвержен предупреждениям компилятора . Чтобы исключить предупреждение, не нанося вред вашему компилятору, вам понадобится явное приведение. Например,

unsigned int flags = static_cast<unsigned int>(-1);

Явное приведение требует, чтобы вы обратили внимание на тип цели. Если вы обращаете внимание на тип цели, то естественно избегаете ловушек других подходов.

Я бы посоветовал обратить внимание на тип цели и убедиться, что нет неявных преобразований. Например:

unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);

Все это правильно и более очевидно для ваших коллег-программистов.

А с C ++ 11 : мы можем использовать auto, чтобы сделать любое из этих правил еще проще:

auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);

Я считаю правильным и очевидным лучше, чем просто правильным.

9 голосов
/ 02 августа 2010

Преобразование -1 в любой беззнаковый тип гарантировано стандартом , чтобы получить все единицы. Использование ~0U обычно некорректно, поскольку 0 имеет тип unsigned int и не заполнит все биты большего типа без знака, если вы явно не напишите что-то вроде ~0ULL. В здравомыслящих системах ~0 должно быть идентично -1, но поскольку стандарт допускает представления с одним дополнением и знаком / величиной, строго говоря, он не переносим.

Конечно, всегда можно выписать 0xffffffff, если вы знаете, что вам нужно ровно 32 бита, но преимущество -1 в том, что оно будет работать в любом контексте, даже если вы не знаете размер шрифта, например макросы, которые работают с несколькими типами, или если размер типа зависит от реализации. Если вы знаете тип, другой безопасный способ получить все - макросы предела UINT_MAX, ULONG_MAX, ULLONG_MAX и т. Д.

Лично я всегда использую -1. Это всегда работает, и вам не нужно об этом думать.

5 голосов
/ 01 мая 2009

Пока у вас есть #include <limits.h> как одно из ваших включений, вы должны просто использовать

unsigned int flags = UINT_MAX;

Если вы хотите получить биты длинного, вы можете использовать

unsigned long flags = ULONG_MAX;

Эти значения гарантированно будут иметь все биты значения результата, равного 1, независимо от того, как реализованы целые числа со знаком.

5 голосов
/ 16 июля 2013

Да. Как упоминалось в других ответах, -1 является наиболее переносимым; однако, это не очень семантическое и вызывает предупреждения компилятора.

Чтобы решить эти проблемы, попробуйте этот простой помощник:

static const struct All1s
{
    template<typename UnsignedType>
    inline operator UnsignedType(void) const
    {
        return static_cast<UnsignedType>(-1);
    }
} ALL_BITS_TRUE;

Использование:

unsigned a = ALL_BITS_TRUE;
uint8_t  b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;
3 голосов
/ 01 мая 2009

Я бы не делал -1. Это довольно не интуитивно понятно (по крайней мере для меня). Присвоение подписанных данных переменной без знака только кажется нарушением естественного порядка вещей.

В вашей ситуации я всегда использую 0xFFFF. (Используйте правильное количество Fs для переменного размера, конечно.)

[Кстати, я очень редко вижу трюк -1, выполненный в реальном коде.]

Кроме того, если вы действительно заботитесь об отдельных битах в vairable, было бы неплохо начать использовать типы uint8_t, uint16_t, uint32_t фиксированной ширины.

...