Безопасно ли использовать -1, чтобы установить все биты в true?

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

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

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

21 ответ

Решение

Я рекомендую вам сделать это именно так, как вы показали, так как это самый прямой. Инициализировать в -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 будет работать с каждым типом и машиной.

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

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

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

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

unsigned int flags = 0;
flags = ~flags;

Портативный и в точку.

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);

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

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

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

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

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

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

Да. Как уже упоминалось в других ответах, -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;

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

unsigned int flags = UINT_MAX;

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

unsigned long flags = ULONG_MAX;

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

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

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

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

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

См. Ответ Литба для очень четкого объяснения проблем.

Я не согласен с тем, что, строго говоря, нет никаких гарантий ни для одного случая. Я не знаю ни одной архитектуры, которая бы не представляла беззнаковое значение "один меньше двух в степени количества битов" в виде всех установленных бит, но вот что на самом деле говорит Стандарт (3.9.1/7 плюс примечание 44):

Представления целочисленных типов должны определять значения с использованием чисто двоичной системы счисления. [Примечание 44:] Позиционное представление для целых чисел, использующее двоичные цифры 0 и 1, в которых значения, представленные последовательными битами, являются аддитивными, начинаются с 1 и умножаются на последовательную целую степень 2, за исключением, возможно, бита с самая высокая позиция.

Это оставляет возможность для одного из битов быть чем-либо вообще.

На процессорах Intel IA-32 можно записать 0xFFFFFFFF в 64-битный регистр и получить ожидаемые результаты. Это связано с тем, что IA32e (64-разрядное расширение для IA32) поддерживает только 32-разрядные непосредственные соединения. В 64-битных инструкциях 32-битные непосредственные значения расширяются до 64-битных.

Следующее незаконно:

mov rax, 0ffffffffffffffffh

Следующее ставит 64 1 с в RAX:

mov rax, 0ffffffffh

Просто для полноты, следующее помещает 32 1 в нижнюю часть RAX (он же EAX):

mov eax, 0ffffffffh

И на самом деле у меня были неудачные программы, когда я хотел записать 0xffffffff в 64-битную переменную, и вместо этого я получил 0xffffffffffffffff. В C это будет:

uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);

результат:

x is 0xffffffffffffffff

Я подумал опубликовать это как комментарий ко всем ответам, в которых говорилось, что 0xFFFFFFFF предполагает 32 бита, но так много людей ответили на него, что я решил добавить его как отдельный ответ.

Хотя 0xFFFF (или же 0xFFFFFFFFи т. д.) может быть легче читать, это может нарушить переносимость кода, который в противном случае был бы переносимым. Рассмотрим, например, библиотечную процедуру для подсчета того, сколько элементов в структуре данных имеют определенные установленные биты (точные биты задаются вызывающей стороной). Процедура может быть абсолютно независимой от того, что представляют биты, но все же должна иметь константу "все биты установлены". В таком случае -1 будет намного лучше, чем шестнадцатеричная константа, поскольку она будет работать с любым размером бит.

Другая возможность, если typedef значение используется для битовой маски, будет использовать ~(bitMaskType)0; если битовая маска имеет тип 16-битный, то в этом выражении будет установлено только 16 бит (даже если в противном случае 'int' будет 32 бита), но поскольку 16 бит будут все, что требуется, все должно быть хорошо при условии, что на самом деле использует соответствующий тип в Typecast.

Кстати, выражения вида longvar &= ~[hex_constant] иметь неприятную ошибку, если шестнадцатеричная константа слишком велика, чтобы поместиться в int, но поместится в unsigned int, Если int 16 бит, то longvar &= ~0x4000; или же longvar &= ~0x10000; очистит один бит longvar, но longvar &= ~0x8000; очистит бит 15 и все биты выше этого. Значения, которые вписываются в int будет применен оператор дополнения к типу int, но результат будет расширен до long, устанавливая верхние биты. Значения, которые слишком велики для unsigned int будет применен оператор дополнения к типу long, Однако значения между этими размерами будут применять оператор дополнения к типу unsigned int, который затем будет преобразован в тип long без расширения знака.

Практически: да

Теоретически: нет.

-1 = 0xFFFFFFFF (или любого другого размера на вашей платформе) верно только для арифметики с двумя дополнениями. На практике это будет работать, но есть устаревшие машины (мэйнфреймы IBM и т. Д.), Где у вас есть действительный знаковый бит, а не двоичное представление. Ваше предложенное ~0 решение должно работать везде.

Как уже упоминалось, -1 - это правильный способ создания целого числа, которое будет преобразовывать в тип без знака со всеми битами, установленными в 1. Однако наиболее важная вещь в C++ - использование правильных типов. Поэтому правильный ответ на вашу проблему (который включает в себя ответ на заданный вами вопрос) заключается в следующем:

std::bitset<32> const flags(-1);

Это всегда будет содержать точное количество битов, которые вам нужны. Это конструирует std::bitset со всеми битами, установленными в 1 по тем же причинам, которые указаны в других ответах.

Используя тот факт, что назначение всех битов одному для типа без знака эквивалентно принятию максимально возможного значения для данного типа,
и расширение области вопроса на все беззнаковые целочисленные типы:

Присвоение -1 работает для любого целого типа без знака (int без знака, uint8_t, uint16_t и т. Д.) Для C и C++.

В качестве альтернативы для C++ вы можете:

  1. Включают<limits>и использоватьstd::numeric_limits< your_type >::max()
  2. Напишите пользовательскую шаблонную функцию(это также позволило бы провести некоторую проверку работоспособности, т. Е. Если тип назначения действительно является неподписанным типом)

Целью может быть добавление большей ясности, так как назначение -1 всегда будет нужен пояснительный комментарий.

Дополнительная попытка подчеркнуть, почему подход Адриана Маккарти здесь может быть лучшим решением, по крайней мере, начиная с С++11, с точки зрения компромисса между соответствием стандарту, безопасностью типов/явной ясностью и уменьшением возможных неоднозначностей:

      unsigned int flagsPreCpp11 = ~static_cast<unsigned int>(0);
auto flags = ~static_cast<unsigned int>(0); // C++11 initialization
predeclaredflags = ~static_cast<decltype(predeclaredflags)>(0); // C++11 assignment to already declared variable

Ниже я подробно объясню свои предпочтения. Как совершенно правильно упомянул Йоханнес, основным источником раздражения здесь является вопрос о значении и семантике представления битов, а также о том, о каких именно типах мы говорим (тип присваиваемого значения и тип возможной интегральной константы времени компиляции). Поскольку нет стандартного встроенного механизма для явного обеспечения установки всех битов в 1 для конкретного случая использования OP о целочисленных значениях без знака, очевидно, что здесь невозможно быть полностью независимым от семантики значений (std::bitset является обычным чистым контейнером, ссылающимся на битовый уровень, но вопрос был о целых числах без знака в целом). Но мы могли бы уменьшить двусмысленность здесь.

Сравнение «лучших» стандартных подходов:

Путь ОП:

      unsigned int flags = -1;

PRO:

  • является "установленным" и коротким
  • довольно интуитивно понятен с точки зрения отношения значения по модулю к «естественному» представлению битового значения.
  • например, изменение целевого типа unsigned на unsigned long возможно без каких-либо дополнительных адаптаций.

Минусы:

  • По крайней мере, новички могут быть не уверены в соответствии стандарту («Должен ли я беспокоиться о битах заполнения?»).
  • Нарушает диапазоны типов (в более тяжелой форме: со знаком или без знака).
  • Исключительно из кода вы не видите непосредственно какой-либо ассоциации битовой семантики.

Ссылаясь на максимальные значения через определения:

      unsigned int flags = UINT_MAX;

Это позволяет обойти проблему перехода со знаком без знака в подходе -1, но вводит несколько новых проблем: несомненно, нужно снова дважды посмотреть здесь, самое позднее, если вы хотите изменить целевой тип, например, на unsigned long. И здесь нужно быть уверенным в том, что максимальное значение приводит к тому, что все биты по стандарту установлены в 1 (и снова касается бита заполнения). Битовая семантика также не очевидна здесь только из кода.

Ссылаясь на максимальные значения более явно:

      auto flags = std::numeric_limits<unsigned int>::max();

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

Подход Адриана (и почему я думаю, что он предпочтительнее до С++11 и с тех пор):

      unsigned int flagsPreCpp11 = ~static_cast<unsigned int>(0);
auto flagsCpp11 = ~static_cast<unsigned int>(0);

PRO:

  • Используется только простейшая интегральная константа времени компиляции: 0. Таким образом, нет необходимости беспокоиться о дальнейшем представлении битов или (неявных) приведения типов. С интуитивной точки зрения, я думаю, мы все можем согласиться с тем фактом, что битовое представление для нуля обычно яснее, чем для максимальных значений, а не только для беззнаковых интегралов.
  • Никакой двусмысленности типов не задействовано, никаких дополнительных поисков не требуется.
  • Здесь задействована явная битовая семантика через дополнение ~. Так что из кода совершенно ясно, каково было намерение. Также очень четко указано, к какому типу и диапазону типов применяется дополнение.

Минусы:

Например, если он назначен члену, есть небольшая вероятность того, что вы не сопоставите типы с пре-С++11:

Объявление в классе:

      unsigned long m_flags;

Инициализация в конструкторе:

      m_flags(~static_cast<unsigned int>(0))

Но, начиная с C++11, использование decltype + auto эффективно предотвращает большинство этих возможных проблем. И некоторые из этих сценариев несоответствия типов (например, на границах интерфейса) также возможны для подхода -1.

Надежный окончательный подход С++11 для предварительно объявленных переменных:

      m_flags(~static_cast<decltype(m_flags)>(0)) // member initialization case

Таким образом, имея полное представление о взвешивании плюсов и минусов всех подходов, я рекомендую этот подход как предпочтительный, по крайней мере, начиная с C++11.

Обновление: благодаря подсказке Эндрю Хенле я удалил утверждение о его удобочитаемости, поскольку это может быть слишком субъективным утверждением. Но я все еще думаю, что его читабельность, по крайней мере, не хуже, чем у большинства подходов с максимальным значением или с явным предоставлением максимального значения через интегралы/литералы времени компиляции, поскольку static_cast-использование также "установлено" и встроено в отличие от определяет/макросы и даже стандартную библиотеку.

Это, безусловно, безопасно, так как -1 всегда будет иметь все доступные биты, но мне больше нравится ~0. -1 просто не имеет особого смысла для unsigned int, 0xFF... не хорошо, потому что это зависит от ширины шрифта.

Способ сделать значение немного более очевидным, но при этом избежать повторения типа:

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

Я говорю:

int x;
memset(&x, 0xFF, sizeof(int));

Это всегда даст вам желаемый результат.

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

например, на большинстве машин целое число составляет 2 байта = 16-битное максимальное значение, которое оно может содержать, составляет 2 ^ 16-1 = 65535 2 ^ 16 = 65536

0% 65536 = 0 -1% 65536 = 65535, что соответствует 1111............. 1 и все биты установлены в 1 (если мы рассмотрим классы вычетов мод 65536), следовательно, это очень прямо вперед.

Похоже

нет, если вы рассмотрите это понятие, оно идеально подходит для неподписанных целых, и это действительно работает

просто проверьте следующий фрагмент программы

int main () {

unsigned int a=2;

cout<<(unsigned int)pow(double(a),double(sizeof(a)*8));

unsigned int b=-1;

cout<<"\n"<<b;

getchar();

return 0;

}

ответ для b = 4294967295, который равен -1%2^32 на 4-байтовых целых числах

следовательно, это совершенно верно для целых чисел без знака

в случае каких-либо расхождений, пожалуйста, сообщите

Другие вопросы по тегам