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

时间:2009-04-30 21:37:03

标签: c++ c binary bit-fields

我已经看到这种模式在C& C ++。

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

这是一种很好的便携式方法吗?或者更好地使用0xffffffff~0

21 个答案:

答案 0 :(得分:152)

我建议你完全按照你所展示的那样去做,因为它是最直接的。初始化为-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。在一个补码机器上,它产生零。因此,在一个补码机器上,上面将a初始化为零。

你应该理解的是,这一切都与价值观有关 - 而不是比特。该变量使用初始化。如果在初始值设定项中修改了用于初始化的变量的位,则将根据这些位生成该值。将a初始化为最高可能值所需的值为-1UINT_MAX。第二个将取决于a的类型 - 您需要ULONG_MAX使用unsigned long。但是,第一种不依赖于它的类型,这是获得最高价值的好方法。

我们在谈论-1是否所有位都是(它并不总是有)。而且我们在谈论~0是否所有位都是(它当然有)。

但我们所讨论的是初始化flags变量的结果是什么。对于它,-1 将适用于所有类型和机器。

答案 1 :(得分:47)

  • unsigned int flags = -1;是便携式的。
  • unsigned int flags = ~0;不可移植,因为它 依赖于二进制表示。
  • unsigned int flags = 0xffffffff;不可移植,因为 它采用32位整数。

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

答案 2 :(得分:24)

坦率地说,我认为所有的fff都更具可读性。至于它是反模式的评论,如果你真的关心所有的位都被设置/清除,我会争辩说你可能处于这样一种情况,你无论如何都要关心变量的大小,这会要求像boost这样的东西:: uint16_t等。

答案 3 :(得分:17)

避免上述问题的方法就是:

unsigned int flags = 0;
flags = ~flags;

便携式和重点。

答案 4 :(得分:13)

我不确定在C ++中首先使用unsigned int作为标志是一个好主意。比特怎么样?

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

答案 5 :(得分:11)

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

我认为正确而明显胜于简单纠正。

答案 6 :(得分:9)

将-1转换为任何无符号类型由标准保证,以产生全1。使用~0U通常很糟糕,因为0的类型为unsigned int,并且不会填充较大的无符号类型的所有位,除非您明确写出~0ULL之类的内容。在理智的系统上,~0应与-1相同,但由于标准允许补码和符号/幅度表示,严格来说它不可移植。

当然,如果你知道你需要正好32位,那么写出0xffffffff总是可以的,但是-1的优势在于,即使你不知道类型的大小,它也可以在任何环境中工作,例如,可以处理多种类型的宏,或者类型的大小因实现而异。如果您确实知道类型,另一种安全的方法就是限制宏UINT_MAXULONG_MAXULLONG_MAX等。

我个人总是使用-1。它总是有效,你不必考虑它。

答案 7 :(得分:5)

是。正如其他答案所述,-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;

答案 8 :(得分:5)

只要您拥有#include <limits.h>作为其中一个,就应该使用

unsigned int flags = UINT_MAX;

如果你想要一个很长的比特,你可以使用

unsigned long flags = ULONG_MAX;

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

答案 9 :(得分:3)

我不会做-1的事情。这是非直观的(至少对我而言)。将签名数据分配给无符号变量似乎违反了事物的自然顺序。

在您的情况下,我总是使用0xFFFF。 (当然,可以使用正确数量的Fs作为可变大小。)

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

此外,如果您真的关心可修复的各个位,最好开始使用固定宽度uint8_tuint16_tuint32_t类型。

答案 10 :(得分:2)

请参阅litb的答案,以便对问题进行非常明确的解释。

我的不同意见是,严格来说,两种情况都无法保证。我不知道任何体系结构都没有表示无符号值,比所有位设置的“一个小于2的功率”,但这是标准实际所说的(3.9.1 / 7加注44):

  

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

这样就可以让其中一个位成为任何东西。

答案 11 :(得分:2)

在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位,但是很多人回答它我想我会把它作为一个单独的答案添加。

答案 12 :(得分:1)

尽管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类型而不添加符号。

答案 13 :(得分:1)

正如其他人所提到的,-1是创建一个整数的正确方法,该整数将转换为无符号类型,所有位都设置为1.但是,C ++中最重要的是使用正确的类型。因此,问题的正确答案(包括您提出的问题的答案)是:

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

这将始终包含您需要的确切位数。它构造一个std::bitset,所有位都设置为1,原因与其他答案中提到的相同。

答案 14 :(得分:1)

实际上:是的

理论上:不。

-1 = 0xFFFFFFFF(或者你的平台上的int的大小)只有两个补码算法才有效。在实践中,它会起作用,但是那里有遗留机器(IBM大型机等),你有一个实际的符号位而不是二进制补码表示。你提议的〜0解决方案应该适用于所有地方。

答案 15 :(得分:0)

这当然是安全的,因为-1将始终设置所有可用位,但我更喜欢~0。 -1对unsigned int没有多大意义。 0xFF ...不好,因为它取决于类型的宽度。

答案 16 :(得分:0)

我说:

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

这将始终为您提供所需的结果。

答案 17 :(得分:0)

利用将无符号类型的所有位分配给1的事实等同于获取给定类型的最大可能值,
并将问题的范围扩展到所有 unsigned 整数类型:

C和C ++的

Assigning -1 works for any unsigned integer type(unsigned int,uint8_t,uint16_t等)。

作为替代方案,对于C ++,您可以:

  1. 包括<limits>并使用std::numeric_limits< your_type >::max()
  2. 写一个custom templated function(这也可以进行一些健全性检查,即目的地类型是否真的是无符号类型)
  3. 目的可以更加清晰,因为分配-1总是需要一些解释性的评论。

答案 18 :(得分:0)

一种使含义更明显但又避免重复输入的方法:

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

答案 19 :(得分:0)

另外努力强调,就标准一致性、类型安全/明确清晰和减少可能的歧义之间的折衷而言,为什么 Adrian McCarthy 的方法可能是自 C++11 以来最迟的最佳解决方案:< /strong>

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 类型更改为 unsigned long,无需任何进一步的调整

缺点:

  • 至少初学者可能不确定是否符合标准(“我是否需要关注填充位?”)。
  • 违反类型范围(以更严重的方式:有符号与无符号)。
  • 仅从代码中,您不会直接看到任何位语义关联。

通过定义引用最大值:

unsigned int flags = UINT_MAX;

这规避了 -1 方法的有符号无符号转换问题,但引入了几个新问题: 有疑问,如果您想将目标类型更改为 unsigned 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。所以不用担心进一步的位表示或(隐式)强制转换是合理的。从直观的角度来看,我认为我们都同意这样一个事实,即零的位表示通常比最大值更清晰,不仅仅是无符号积分。
  • 不涉及类型歧义,无需进一步查询。
  • 此处通过补码 ~ 涉及显式位语义。所以从代码中可以很清楚地看出意图是什么。而且它也很明确,在哪个类型和类型范围上应用补语。

缺点:

例如,如果分配给一个成员,则您与 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 也是“建立”的并且是内置的定义/宏甚至标准库。

答案 20 :(得分:-6)

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

例如在大多数机器中,一个整数是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字节整数

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

如果有任何差异,请发送报告