我已经看到这种模式在 C 和 C++ 中使用了很多。
unsigned int flags = -1; // all bits are true
这是实现此目的的一种很好的便携方式吗?还是正在使用0xffffffff
或~0
更好?
我已经看到这种模式在 C 和 C++ 中使用了很多。
unsigned int flags = -1; // all bits are true
这是实现此目的的一种很好的便携方式吗?还是正在使用0xffffffff
或~0
更好?
我建议您完全按照您的说明进行操作,因为它是最直接的。Initialize to -1
which will always work ,独立于实际的符号表示,而~
有时会出现令人惊讶的行为,因为您必须拥有正确的操作数类型。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
为零。
您应该理解的是,这一切都与价值有关,而不是位。变量用value初始化。如果在初始化程序中修改了用于初始化的变量的位,则将根据这些位生成值。初始化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);
我认为正确和明显比简单正确更好。
我不确定在 C++ 中首先使用 unsigned int 作为标志是一个好主意。bitset 之类的呢?
std::numeric_limit<unsigned int>::max()
更好,因为0xffffffff
假设 unsigned int 是一个 32 位整数。
标准保证将 -1 转换为任何无符号类型会导致全一。使用~0U
通常很糟糕,因为0
has 类型unsigned int
并且不会填充更大的无符号类型的所有位,除非您明确编写类似~0ULL
. 在健全的系统上,~0
应该与 相同-1
,但由于标准允许补码和符号/大小表示,严格来说它不是可移植的。
当然,0xffffffff
如果你知道你需要正好 32 位,写出来总是可以的,但是 -1 的优点是它可以在任何上下文中工作,即使你不知道类型的大小,例如适用于多种类型的宏,或者如果类型的大小因实现而异。如果您知道类型,另一种获得全一的安全方法是限制宏UINT_MAX
、ULONG_MAX
、ULLONG_MAX
等。
我个人总是使用-1。它总是有效的,你不必考虑它。
只要你有#include <limits.h>
作为你的包括之一,你应该只使用
unsigned int flags = UINT_MAX;
如果你想要很长的位,你可以使用
unsigned long flags = ULONG_MAX;
无论有符号整数如何实现,这些值都保证将结果集的所有值位设置为 1。
是的。正如其他答案中提到的,-1
是最便携的;但是,它不是很语义化并且会触发编译器警告。
为了解决这些问题,试试这个简单的助手:
static const struct All1s
{
template<typename UnsignedType>
inline operator UnsignedType(void) const
{
static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
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;
我不会做-1的事情。这是相当不直观的(至少对我来说)。将有符号数据分配给无符号变量似乎违反了事物的自然顺序。
在你的情况下,我总是使用0xFFFF
. (当然,为可变大小使用正确数量的 F。)
[顺便说一句,我很少看到在实际代码中使用 -1 技巧。]
此外,如果您真的关心变量中的各个位,那么最好开始使用固定宽度uint8_t
, uint16_t
,uint32_t
类型。
在 Intel 的 IA-32 处理器上,可以将 0xFFFFFFFF 写入 64 位寄存器并获得预期结果。这是因为 IA32e(IA32 的 64 位扩展)仅支持 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 位的答案的评论发布,但有很多人回答了它,我想我会将其添加为单独的答案。
有关这些问题的非常清楚的解释,请参阅 litb 的答案。
我的不同意见是,非常严格地说,这两种情况都不能保证。我不知道有任何体系结构不代表所有位设置的无符号值“小于 2 的位数的幂”,但这是标准实际所说的(3.9.1/7 plus注 44):
整数类型的表示应使用纯二进制计数系统定义值。[注 44:] 使用二进制数字 0 和 1 的整数的位置表示,其中由连续位表示的值是相加的,从 1 开始,并乘以 2 的连续整数幂,可能除了具有最高的位置。
这使得其中一个位有可能成为任何东西。
尽管0xFFFF
(or0xFFFFFFFF
等) 可能更易于阅读,但它可能会破坏代码的可移植性,否则这些代码本来是可移植的。例如,考虑一个库例程来计算数据结构中有多少项设置了某些位(确切的位由调用者指定)。该例程可能完全不知道这些位代表什么,但仍然需要有一个“所有位设置”常量。在这种情况下,-1 将比十六进制常量好得多,因为它适用于任何位大小。
如果一个typedef
值用于位掩码,另一种可能性是使用 ~(bitMaskType)0; 如果位掩码恰好是 16 位类型,则该表达式将仅设置 16 位(即使 'int' 否则将是 32 位),但由于 16 位将是所有必需的,只要有一个就可以了实际上在类型转换中使用了适当的类型。
longvar &= ~[hex_constant]
顺便说一句,如果十六进制常数太大而无法放入 ,则表单的表达式有一个讨厌的陷阱int
,但可以放入unsigned int
. 如果 anint
是 16 位,则longvar &= ~0x4000;
or longvar &= ~0x10000
; 将清除 的一位longvar
,但longvar &= ~0x8000;
将清除第 15 位和高于该位的所有位。适合的值int
将补码运算符应用于 type int
,但结果将符号扩展为long
,设置高位。太大的值unsigned int
会将补码运算符应用于 type long
。但是,介于这些大小之间的值会将补码运算符应用于 type unsigned int
,然后将其转换为long
没有符号扩展的 type 。
正如其他人所提到的,-1 是创建整数的正确方法,该整数将转换为所有位都设置为 1 的无符号类型。但是,C++ 中最重要的是使用正确的类型。因此,您的问题的正确答案(包括您提出的问题的答案)是这样的:
std::bitset<32> const flags(-1);
这将始终包含您需要的确切位数。std::bitset
出于其他答案中提到的相同原因,它构造了所有位设置为 1 的 a 。
这当然是安全的,因为 -1 将始终设置所有可用位,但我更喜欢 ~0。-1 对于unsigned int
. 0xFF
...不好,因为它取决于类型的宽度。
实际上:是的
理论上:不会。
-1 = 0xFFFFFFFF(或您平台上的任何大小的 int)仅适用于二进制补码算法。在实践中,它会起作用,但是那里有遗留机器(IBM 大型机等),你有一个实际的符号位而不是二进制补码表示。您提出的 ~0 解决方案应该适用于任何地方。
我说:
int x;
memset(&x, 0xFF, sizeof(int));
这将始终为您提供所需的结果。
另一个努力要强调的是,为什么 Adrian McCarthy 的方法可能是自 C++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
我将在下面详细解释我的偏好。正如约翰内斯完全正确地提到的那样,这里激怒的根本原因是关于值与根据位表示语义以及我们正在谈论的确切类型(分配的值类型与可能的编译时间积分常量的类型)的问题。由于没有标准的内置机制来明确确保对于 OP 关于无符号整数值的具体用例将所有位设置为 1,因此很明显,这里不可能完全独立于值语义(std::bitset是一个常见的纯位层引用容器,但问题一般是关于无符号整数)。但我们也许可以减少这里的歧义。
“更好”的符合标准的方法的比较:
OP的方式:
unsigned int flags = -1;
优点:
缺点:
通过定义参考最大值:
unsigned int flags = UINT_MAX;
这规避了 -1 方法的有符号无符号转换问题,但引入了几个新问题:如果您想将目标类型更改为无符号长整数,则必须再次查看这里两次。在这里,必须确定这样一个事实,即最大值导致标准将所有位设置为 1(以及再次填充位问题)。位语义在这里也不是很明显,直接来自代码。
更明确地引用最大值:
auto flags = std::numeric_limits<unsigned int>::max();
在我看来,这是更好的最大值方法,因为它是免费的宏/定义,并且对所涉及的类型是明确的。但是关于方法类型本身的所有其他问题仍然存在。
Adrian 的方法(以及为什么我认为它是 C++11 之前的首选方法):
unsigned int flagsPreCpp11 = ~static_cast<unsigned int>(0);
auto flagsCpp11 = ~static_cast<unsigned int>(0);
优点:
缺点:
例如,如果分配给一个成员,那么您很有可能将类型与 C++11 之前的类型不匹配:
课堂声明:
unsigned long m_flags;
构造函数中的初始化:
m_flags(~static_cast<unsigned int>(0))
但是从 C++11 开始,使用 decltype + auto 非常强大,可以防止大多数可能出现的问题。并且这些类型不匹配的场景中的一些(例如在接口边界上)对于 -1 方法也是可能的。
用于预声明变量的强大的最终 C++11 方法:
m_flags(~static_cast<decltype(m_flags)>(0)) // member initialization case
因此,在这里全面了解所有方法的优缺点的权重,我推荐这种方法作为首选方法,最迟从 C++11 开始。
更新:感谢 Andrew Henle 的提示,我删除了关于其可读性的声明,因为这可能是一个过于主观的声明。但我仍然认为,它的可读性至少不比大多数最大值方法或那些通过编译时积分/文字明确提供最大值的方法差,因为 static_cast-usage 也“建立”并且内置于定义/宏,甚至是标准库。
一种使含义更明显但又避免重复类型的方法:
const auto flags = static_cast<unsigned int>(-1);
利用将所有位分配给无符号类型的事实等价于获取给定类型的最大可能值,
并将问题的范围扩展到所有无符号整数类型:
分配 -1 适用于 C 和 C++ 的任何无符号整数类型(无符号整数、uint8_t、uint16_t 等)。
作为替代方案,对于 C++,您可以:
<limits>
和使用std::numeric_limits< your_type >::max()
目的可能是增加清晰度,因为分配-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 的答案在 4 字节整数上是 -1%2^32
因此它对无符号整数完全有效
如有任何差异请报告