Безопасно ли использовать -1 для установки всех битов в значение true?
-
03-07-2019 - |
Вопрос
Я видел, как этот шаблон часто используется в C и C++.
unsigned int flags = -1; // all bits are true
Это хороший портативный способ добиться этого?Или использует 0xffffffff
или ~0
лучше?
Решение
Я рекомендую вам сделать это именно так, как вы показали, поскольку это наиболее простой вариант.Инициализировать до -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-битный INT.
Если вы хотите установить все биты способом, гарантированным стандартом C, используйте первый.
Честно говоря, я думаю, что все fff более читабельны.Что касается комментария о том, что это антишаблон, то если вас действительно волнует, чтобы все биты были установлены/очищены, я бы сказал, что вы, вероятно, находитесь в ситуации, когда вас все равно волнует размер переменной, что потребует чего-то вроде повышения ::uint16_t и т. д.
Чтобы избежать упомянутых проблем, нужно просто сделать:
unsigned int flags = 0;
flags = ~flags;
Портативно и по делу.
Я не уверен, что использование беззнакового целого числа для флагов является хорошей идеей в первую очередь в C++.А как насчет битсета и тому подобного?
std::numeric_limit<unsigned int>::max()
лучше, потому что 0xffffffff
предполагает, что unsigned int — это 32-битное целое число.
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);
Все это правильно и более очевидно вашим коллегам-программистам.
И с С++ 11:Мы можем использовать auto
чтобы сделать любое из них еще проще:
auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);
Я считаю правильное и очевидное лучше, чем просто правильное.
Преобразование -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
.(Конечно, используйте правильное количество F для переменного размера.)
[Кстати, я очень редко вижу трюк -1 в реальном коде.]
Кроме того, если вас действительно интересуют отдельные биты переменной, было бы неплохо начать использовать переменную с фиксированной шириной. uint8_t
, uint16_t
, uint32_t
типы.
На процессорах Intel IA-32 можно записать 0xFFFFFFFF в 64-битный регистр и получить ожидаемые результаты.Это связано с тем, что IA32e (64-битное расширение IA32) поддерживает только 32-битные немедленные операции.В 64-битных инструкциях используются 32-битные непосредственные инструкции. расширенный знаком до 64-бит.
Незаконным является следующее:
mov rax, 0ffffffffffffffffh
Следующее помещает 64 единицы в RAX:
mov rax, 0ffffffffh
Для полноты картины ниже в нижней части RAX (он же EAX) помещаются 32 единицы:
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 бита, но на него ответило так много людей, что я решил добавить это как отдельный ответ.
См. ответ Литба для очень четкого объяснения проблем.
Я не согласен с тем, что, строго говоря, ни в том, ни в другом случае нет никаких гарантий.Я не знаю ни одной архитектуры, которая не представляла бы беззнаковое значение «на единицу меньше двух в степени числа битов», поскольку все установленные биты, но вот что на самом деле говорит Стандарт (3.9.1/7 плюс примечание 44):
Представления целочисленных типов должны определять значения с использованием чисто двоичной системы счисления.[Примечание 44:] Позиционное представление целых чисел, в котором используются двоичные цифры 0 и 1, в котором значения, представленные последовательными битами, являются аддитивными, начинаются с 1 и умножаются на последовательную целую степень 2, за исключением, возможно, бита с высшая должность.
Это оставляет возможность того, что один из битов может быть чем угодно.
Практически:Да
Теоретически:Нет.
-1 = 0xFFFFFFFF (или любой другой размер int на вашей платформе) верно только для арифметики с дополнением до двух.На практике это будет работать, но существуют устаревшие машины (мэйнфреймы IBM и т. д.), на которых у вас есть фактический знаковый бит, а не представление с дополнением до двух.Предложенное вами решение ~0 должно работать везде.
Хотя 0xFFFF
(или 0xFFFFFFFF
, и т. д.) может быть легче читать, это может нарушить переносимость кода, который в противном случае был бы переносимым.Рассмотрим, например, библиотечную процедуру для подсчета количества элементов в структуре данных, в которых установлены определенные биты (точные биты задаются вызывающей стороной).Процедура может быть совершенно независимой от того, что представляют собой биты, но все равно должна иметь константу «все биты установлены».В таком случае -1 будет намного лучше, чем шестнадцатеричная константа, поскольку она будет работать с любым битовым размером.
Другая возможность, если typedef
значение используется для битовой маски, следует использовать ~(bitMaskType)0;если битовая маска имеет только 16-битный тип, в этом выражении будет установлено только 16 бит (даже если в противном случае 'int' было бы 32 бита), но поскольку 16 бит - это все, что требуется, все должно быть в порядке предоставил что на самом деле используется соответствующий тип в приведении типов.
Кстати, выражения вида 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 — это правильный способ создать целое число, которое будет преобразовано в беззнаковый тип со всеми битами, установленными в 1.Однако самое важное в C++ — использование правильных типов.Следовательно, правильный ответ на вашу проблему (включая ответ на заданный вами вопрос) таков:
std::bitset<32> const flags(-1);
Он всегда будет содержать точное количество бит, которое вам нужно.Он создает std::bitset
со всеми битами, установленными в 1 по тем же причинам, которые указаны в других ответах.
Это, конечно, безопасно, так как при -1 всегда будут установлены все доступные биты, но мне больше нравится ~0.-1 просто не имеет особого смысла для unsigned int
. 0xFF
...это не хорошо, потому что это зависит от ширины шрифта.
Я говорю:
int x;
memset(&x, 0xFF, sizeof(int));
Это всегда даст вам желаемый результат.
Используя тот факт, что присвоение всем битам одного для беззнакового типа эквивалентно взятию максимально возможного значения для данного типа,
и распространить сферу вопроса на все без подписи целочисленные типы:
Присвоение -1 работает для любого без подписи целочисленный тип (unsigned int, uint8_t, uint16_t и т. д.) как для C, так и для C++.
В качестве альтернативы для C++ вы можете:
- Включать
<limits>
и использоватьstd::numeric_limits< your_type >::max()
- Написать пользовательская шаблонная функция (Это также позволило бы провести некоторую проверку работоспособности, т.е.если тип назначения действительно является беззнаковым типом)
Целью может быть добавление большей ясности, поскольку назначение -1
всегда будут нуждаться в пояснительных комментариях.
Способ сделать значение более очевидным и при этом избежать повторения типа:
const auto flags = static_cast<unsigned int>(-1);
да, показанное представление очень правильное, так как если бы мы сделали это наоборот, вам потребовалось бы, чтобы оператор перевернул все биты, но в этом случае логика довольно проста, если мы учтем размер целых чисел в машине
например, в большинстве машин целое число составляет 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-байтовых целых чисел
следовательно, это совершенно справедливо для целых чисел без знака
в случае каких-либо несоответствий, пожалуйста, сообщите