使用 -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
中。但它首先会在unsigned int
中创建一个包含所有位1的模式,然后将其分配给unsigned long
。当~0
有更多位时,会发生的情况是并非所有这些都是1。
考虑这个,在非二进制补码表示中会失败:
unsigned int a = ~0; // Should have done ~0u !
原因是UINT_MAX
必须反转所有位。反转将在两个补码机器上产生ULONG_MAX
(这是我们需要的值!),但不在另一个表示上产生flags
。在一个补码机器上,它产生零。因此,在一个补码机器上,上面将<=>初始化为零。
你应该理解的是,这一切都与价值观有关 - 而不是比特。该变量使用值初始化。如果在初始值设定项中修改了用于初始化的变量的位,则将根据这些位生成该值。将<=>初始化为最高可能值所需的值是<=>或<=>。第二种将取决于<=>的类型 - 您需要使用<=>来获取<=>。但是,第一种不依赖于它的类型,这是获得最高价值的好方法。
我们不谈论<=>是否所有位都是(它并不总是)。而且我们不在讨论<=>是否所有位都有(当然还有)。
但我们所说的是初始化的<=>变量的结果是什么。为此,仅<=> 将适用于所有类型和机器。
其他提示
unsigned int flags = -1;
是便携式的。unsigned int flags = ~0;
无法便携,因为它依赖于两个组合表示。unsigned int flags = 0xffffffff;
不可移植,因为它假设32位INT。
如果您想以 C 标准保证的方式设置所有位,请使用第一个。
坦率地说,我认为所有的fff都更具可读性。至于它的反模式的评论,如果你真的关心所有的位都被设置/清除,我会争辩说你可能处于这样一种情况,你无论如何都要关心变量的大小,这会要求像boost这样的东西:: uint16_t等。
避免上述问题的方法就是:
unsigned int flags = 0;
flags = ~flags;
便携式和重点。
我不确定在C ++中首先使用unsigned int作为标志是一个好主意。比特怎么样?
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);
所有这些都是正确且更明显给你的同事们。
使用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);
我认为正确而明显胜于简单纠正。
将-1转换为任何无符号类型由标准保证,以产生全1。 ~0U
的使用通常很糟糕,因为0
具有类型unsigned int
并且不会填充较大的无符号类型的所有位,除非您明确写出类似~0ULL
的内容。在理智的系统上,~0
应与-1
相同,但由于标准允许补码和符号/幅度表示,严格来说它不可移植。
当然,如果您知道自己只需要32位,那么写出0xffffffff
总是可以的,但-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技巧。]
此外,如果您真的关心可用的各个位,最好开始使用固定宽度uint8_t
,uint16_t
,uint32_t
类型。
在Intel的IA-32处理器上,可以将0xFFFFFFFF写入64位寄存器并获得预期的结果。这是因为IA32e(IA32的64位扩展)仅支持32位立即数。在64位指令中,32位立即符号扩展为64位。
以下是非法的:
mov rax, 0ffffffffffffffffh
以下在RAX中放入64个1:
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位,但是很多人回答它我想我会把它作为一个单独的答案添加。
请参阅litb的答案,以便对问题进行非常明确的解释。
我的不同意见是,严格来说,两种情况都无法保证。我不知道任何体系结构都没有表示无符号值,比所有位设置的“少于2位的功率”,但这是标准实际所说的(3.9.1 / 7加注44):
整数类型的表示应使用纯二进制计算系统定义值。 [注44:]使用二进制数字0和1的整数的位置表示,其中由连续位表示的值是加法的,以1开始,并且乘以2的连续积分幂,除了可能的位最高职位。
这样就可以让其中一个位成为任何东西。
实际上:是的
理论上:不。
-1 = 0xFFFFFFFF(或者你的平台上的int的大小)只有两个补码算法才有效。在实践中,它可以工作,但是有一些遗留机器(IBM大型机等),你有一个实际的符号位而不是二进制补码表示。你提议的〜0解决方案应该适用于所有地方。
虽然0xFFFF
(或0xFFFFFFFF
等)可能更容易阅读,但它可能会破坏代码的可移植性,否则这些代码将是可移植的。例如,考虑一个库例程来计算数据结构中有多少项设置了某些位(确切的位由调用者指定)。例程可能完全不知道比特代表什么,但仍然需要有一个<!>“所有比特设置<!>”;不变。在这种情况下,-1将比十六进制常量好得多,因为它可以用于任何位大小。
另一种可能性,如果<〜>值用于位掩码,则使用〜(bitMaskType)0;如果位掩码恰好只是一个16位类型,那么该表达式只会设置16位(即使'int'否则将是32位)但由于16位将是所有需要的,所以事情应该没问题提供实际上在类型转换中使用了相应的类型。
顺便提一下,如果十六进制常量太大而不适合typedef
,那么表单longvar &= ~[hex_constant]
的表达式会有一个讨厌的问题,但是会适合int
。如果unsigned int
是16位,则longvar &= ~0x4000;
或longvar &= ~0x10000
;将清除一点longvar
,但longvar &= ~0x8000;
将清除第15位以及所有位。适合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 整数类型:
分配-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(如果我们考虑残差类mod 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 whcih是-1%2 ^ 32 4字节整数
因此它对无符号整数完全有效
如果有任何差异,请发送报告