使用 -1将所有位设置为 true 是否安全?

我看到这种模式在 C & C + + 中使用得很多。

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

这是一个很好的便携式方法来实现这一点? 或者使用 0xffffffff~0更好?

41839 次浏览

它当然是安全的,因为 -1总是设置了所有可用的位,但是我更喜欢 ~ 0。-1对于 unsigned int来说没什么意义。0xFF... 是不好的,因为它取决于类型的宽度。

首先,我不确定在 C + + 中使用无符号整型表示标志是否是一个好主意。比特集之类的呢?

std::numeric_limit<unsigned int>::max()更好,因为 0xffffffff假定 unsignedint 是一个32位整数。

实际上: 是的

理论上来说,没有。

-1 = 0xFFFFFFFF (或平台上 int 的任何大小)只有在使用 two 的补数算法时才为真。在实践中,它将工作,但有遗留的机器(IBM 大型机等) ,您已经得到了一个实际的符号位,而不是一个二的补充表示。您提出的 ~ 0解决方案应该在任何地方都适用。

老实说,我觉得所有的 ff 都更易读。至于说它是一个反模式,如果你真的关心所有的位被设置/清除,我会说你可能处在一种情况下,你关心变量的大小无论如何,这将要求类似的东西: : uint16 _ t,等。

我建议你完全按照你所展示的去做,因为这是最直接的一个。初始化为 -1,它将工作 一直都是,独立于实际的符号表示,而 ~有时会有令人惊讶的行为,因为您必须有正确的操作数类型。只有这样,您才能得到 unsigned类型的最高值。

举一个可能令人惊讶的例子,看看下面这个:

unsigned long a = ~0u;

它不一定将所有位1的模式都存储到 a中。但是它首先会创建一个 unsigned int中包含所有位1的模式,然后将其分配给 a。当 unsigned long有更多比特时,并非所有比特都是1。

考虑一下这个例子,它会在非二的补语表示上失败:

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

原因是 ~0必须反转所有位。反过来,将产生 -1对一个二的补充机(这是我们需要的价值!),但将 没有产生的 -1在另一个代表。在1的补数机上,它得到零。因此,在1的补语机器上,上面的代码会将 a初始化为零。

你应该明白的是,这一切都是关于价值-而不是位。该变量用 价值初始化。如果在初始化器中修改了用于初始化的变量的位,则将根据这些位生成值。要将 a初始化到尽可能高的值,所需的值是 -1UINT_MAX。第二个取决于 a的类型-你需要使用 ULONG_MAX作为 unsigned long。但是,第一种方法不依赖于它的类型,而且它是获得最高值的一种很好的方法。

我们正在 没有讨论 -1是否具有所有位1(它并不总是具有)。我们在 没有中讨论 ~0是否具有所有的位1(当然,它具有)。

但是我们要讨论的是初始化的 flags变量的结果是什么。为此,只有 -1将与每种类型和机器一起工作。

我不会做 -1的事情。这是相当不直观的(至少对我来说)。将有符号数据赋值给无符号变量似乎违背了事物的自然规律。

在您的情况下,我总是使用 0xFFFF。(当然,对于可变大小使用正确的 F 数。)

[顺便说一下,我很少在现实世界的代码中看到 -1的技巧。]

此外,如果您真的关心变量中的各个位,那么最好开始使用固定宽度的 uint8_tuint16_tuint32_t类型。

只要你有 #include <limits.h>作为你的包括之一,你应该只是使用

unsigned int flags = UINT_MAX;

如果你想要一大笔钱,你可以用

unsigned long flags = ULONG_MAX;

无论有符号整数是如何实现的,这些值都保证将结果的所有值位设置为1。

  • unsigned int flags = -1;是便携式的。
  • unsigned int flags = ~0;不便携是因为 依赖于两个补语的表示。
  • unsigned int flags = 0xffffffff;不便携是因为 它假设是32位整数。

如果您想以 C 标准保证的方式设置所有位,请使用第一个。

避免上述问题的一种方法是:

unsigned int flags = 0;
flags = ~flags;

便携式,切中要害。

在 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位变量时,我的程序失败了,我得到了一个0xffffffffffffff。在 C 语言中,这是:

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

结果是:

x is 0xffffffffffffffff

我想把这个作为一个评论贴在所有说0xFFFFFF 假设32位的答案上,但是这么多人回答了它,我想我应该把它作为一个单独的答案添加上去。

有关这些问题的非常清楚的解释,请参阅 Litb 的答案。

我的不同意见是,非常严格地说,这两种情况都没有保证。我不知道有什么架构不能代表一个无符号值“一小于二乘以位数的幂”作为所有位的集合,但是这里是标准实际上说的(3.9.1/7加注44) :

积分类型的表示应该使用纯二进制计数系统来定义值。[注44: ]一种用二进制数字0和1表示整数的位置表示法,其中由连续位表示的值是可加的,从1开始,乘以连续的整数幂2,除了可能位置最高的位。

这就留下了其中一个比特可能是任何东西的可能性。

将 -1转换为任何无符号类型是 由标准保证,从而产生 all-one。使用 ~0U通常是不好的,因为 0具有 unsigned int类型,并且不会填充较大的无符号类型的所有位,除非您显式地编写类似于 ~0ULL的代码。在理智的系统上,~0应该与 -1相同,但是由于标准允许补码和符号/大小表示,严格来说,它是不可移植的。

当然,如果你知道你正好需要32位,写出 0xffffffff总是可以的,但是 -1的优点是它可以在任何上下文中工作,即使你不知道类型的大小,比如对多个类型工作的宏,或者如果类型的大小随着实现而变化。如果你知道类型,另一个安全的方法得到所有-一是限制宏 UINT_MAXULONG_MAXULLONG_MAX,等。

就个人而言,我总是使用 -1。它总是工作,你不必考虑它。

是的,所显示的表示是非常正确的,如果我们反过来做,你会需要一个操作员来反转所有的位,但是在这种情况下,逻辑是非常简单的,如果我们考虑机器中整数的大小

例如,在大多数机器中,一个整数是2字节 = 16位最大值是2 ^ 16-1 = 655352 ^ 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的答案是 -1% 2 ^ 32对于4个字节的整数

因此它对无符号整数是完全有效的

如有任何差异,请报告

我说:

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

这总会给你想要的结果。

尽管 0xFFFF(或 0xFFFFFFFF等)可能更容易阅读,但它可能破坏原本可以移植的代码的可移植性。例如,考虑一个库例程来计算一个数据结构中有多少条目设置了某些位(由调用者指定的确切位)。例程可能完全不知道位表示什么,但仍然需要有一个“所有位设置”常量。在这种情况下,-1将大大优于十六进制常量,因为它可以处理任何位大小。

另一种可能性是,如果位掩码使用的是 typedef值,那么使用 ~ (bitMaskType)0; 如果位掩码恰好是16位类型,那么表达式将只有16位设置(即使“ int”本来应该是32位) ,但是因为只需要16位,所以在类型转换中实际使用适当类型的 提供应该没问题。

顺便说一句,如果十六进制常数太大以至于无法适应 int,但适应 unsigned int,那么形式 longvar &= ~[hex_constant]的表达式就有一个棘手的问题。如果一个 int是16位,那么 longvar &= ~0x4000;longvar &= ~0x10000; 将清除一个位的 longvar,但是 longvar &= ~0x8000;将清除位15和所有位以上。适合于 int的值将具有应用于类型 int的补运算符,但是结果将被符号扩展到 int0,设置上位。对于 unsigned int来说太大的值将使补运算符应用于 int0类型。但是,介于这些大小之间的值将对类型 unsigned int应用补运算符,然后将其转换为没有符号扩展的类型 int0。

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的无符号类型。然而,C + + 中最重要的事情是使用正确的类型。因此,你问题的正确答案(包括你所问问题的答案)是:

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

这将始终包含您需要的位的确切数量。它构造一个 std::bitset,所有位都设置为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等效于为给定类型取最大可能值这一事实,
并将问题的范围扩展到所有 没签名整数类型:

赋值 -1适用于 C 和 C + + 的任何 没签名整数类型 (unsignedint,uint8 _ t,uint16 _ t 等)。

作为另一种选择,对于 C + + ,你可以:

  1. 包括 <limits>并使用 std::numeric_limits< your_type >::max()
  2. 编写一个 自定义模板函数(这也允许进行一些健全性检查,例如,如果目标类型实际上是一个无符号类型)

这样做的目的可以更加明确,因为分配 -1总是需要一些解释性的意见。

一种方法,使意思更加明显,但避免重复的类型:

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

另一个需要强调的是,为什么 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

我将在下面详细解释我的偏好。正如 Johannes 完全正确地提到的,这里烦恼的根本原因是关于值与根据位表示语义的问题,以及我们正在谈论的确切类型(赋值类型与可能的编译时间积分常量的类型)。由于没有标准的内置机制来显式地确保在 OP 的具体用例中所有的位都为1,因此很明显,这里不可能完全独立于值语义(std: : bitset 是一个常见的纯位层引用容器,但问题一般是关于无符号整数的)。但我们也许可以减少这里的模棱两可。

“更好的”标准兼容方法的比较:

观察员的方式:

unsigned int flags = -1;

优点:

  • 是“确定的”和简短的
  • 是相当直观的模透视的价值,以“自然”位值表示
  • 例如,不需要任何进一步的修改,就可以将目标无符号类型更改为无符号 long

缺点:

  • 至少初学者可能不确定标准的一致性(“我必须考虑填充位吗?”)。
  • 违反类型范围(以较重的方式: 有符号与无符号)。
  • 仅从代码中,您不会直接看到任何位语义关联。

通过定义引用最大值:

unsigned int flags = UINT_MAX;

这绕过了 -1方法的有符号的无符号转换问题,但是引入了几个新问题: 有疑问的是,如果您想将目标类型更改为无符号的 long,那么您必须在这里重新查看两次。在这里,我们必须确定这样一个事实,即最大值导致所有位被标准设置为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);

优点:

  • 只使用最简单的整数编译时间常数: 0。因此,不必担心进一步的位表示或(隐式)强制转换。从直观的角度来看,我认为我们都同意这样一个事实,即零的位表示通常比最大值的位表示更清楚,而不仅仅是无符号积分。
  • 不涉及类型歧义,不需要进一步查询。
  • 这里通过补语 ~ 涉及到显式位语义。所以从代码中可以很清楚地看出他们的目的是什么。而且补语的类型和类型范围也非常明确。

缺点:

例如,如果分配给某个成员,那么很小的可能性会与 pre C + + 11的类型不匹配:

课堂声明:

unsigned long m_flags;

在构造函数中初始化:

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

但是自从 C + + 11以来,dectype + auto 的使用非常强大,可以防止大多数这些可能的问题。其中一些类型不匹配的场景(例如在接口边界上)对于 -1方法也是可能的。

针对预声明变量的稳健的最终 C + + 11方法:

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

因此,在全面了解了所有方法的优点和缺点的权重后,我推荐这种方法作为首选方法,至少从 C + + 11开始。

更新: 由于 Andrew Henle 的提示,我删除了关于其可读性的声明,因为这可能是一个过于主观的声明。但我仍然认为,它的可读性至少不会比大多数最大值方法或通过编译时积分/文字显式提供最大值的方法差,因为 static _ cast-using 也是“建立”的,而且是内置的,与定义/宏甚至 std-lib 形成对比。