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

问题描述 投票:127回答:20

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

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

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

c++ c binary bit-fields
20个回答
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将适用于所有类型和机器。


3
投票

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

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

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

另外,如果你真的关心可用的各个位,那么最好开始使用固定宽度的qazxsw poi,qazxsw poi,qazxsw poi类型。


2
投票

在Intel的IA-32处理器上,可以将0xFFFFFFFF写入64位寄存器并获得预期的结果。这是因为IA32e(IA32的64位扩展)仅支持32位立即数。在64位指令中,32位立即符号扩展为64位。

以下是非法的:

uint8_t

以下是RAX中的64个1:

uint16_t

为了完整起见,以下将32个1放在RAX(又名EAX)的下半部分:

uint32_t

事实上,当我想将0xffffffff写入64位变量并且我得到一个0xffffffffffffffff时,我的程序失败了。在C中,这将是:

mov rax, 0ffffffffffffffffh

结果是:

mov rax, 0ffffffffh

我想发布这个作为对所有答案的评论,说0xFFFFFFFF假设32位,但是很多人回答它我想我会把它作为一个单独的答案添加。


2
投票

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

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

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

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


1
投票

实际上:是的

理论上:没有。

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


1
投票

尽管mov eax, 0ffffffffh (或uint64_t x; x = UINT64_C(0xffffffff) printf("x is %"PRIx64"\n", x); 等)可能更容易阅读,但它可能会破坏代码的可移植性,否则这些代码将是可移植的。例如,考虑一个库例程来计算数据结构中有多少项设置了某些位(确切的位由调用者指定)。例程可能完全不知道比特代表什么,但仍需要“所有比特设置”不变。在这种情况下,-1将比十六进制常量好得多,因为它可以用于任何位大小。

如果将x is 0xffffffffffffffff 值用于位掩码,则另一种可能性是使用〜(bitMaskType)0;如果位掩码恰好只是一个16位类型,那么该表达式只会设置16位(即使'int'否则将是32位),但由于16位将是所有需要的,所以一切都应该没问题。实际上在类型转换中使用了适当的类型。

顺便说一句,如果十六进制常数太大而不适合0xFFFF,那么0xFFFFFFFF形式的表达式会有一个令人讨厌的问题,但它会适合typedef。如果longvar &= ~[hex_constant]是16位,那么intunsigned int;将清除一点int,但longvar &= ~0x4000;将清除第15位和所有位以上。适合longvar &= ~0x10000的值将补码运算符应用于类型longvar,但结果将符号扩展到longvar &= ~0x8000;,设置高位。对于int而言太大的值将使用补充运算符应用于int类型。但是,这些大小之间的值将应用补码运算符键入long,然后将其转换为类型unsigned int而不带符号扩展名。


1
投票

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

long

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


0
投票

它肯定是安全的,因为-1将始终设置所有可用位,但我更喜欢~0。 -1只是对long没有多大意义。 std::bitset<32> const flags(-1); ...不好,因为它取决于类型的宽度。


0
投票

我说:

std::bitset

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


0
投票

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

C和C ++的unsigned int(unsigned int,uint8_t,uint16_t等)。

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

  1. 包括0xFF并使用int x; memset(&x, 0xFF, sizeof(int));
  2. 写一个Assigning -1 works for any unsigned integer type(这也可以进行一些健全性检查,即目的地类型是否真的是无符号类型)

目的可以更加清晰,因为分配<limits>总是需要一些解释性的评论。


0
投票

一种使意义更明显但又避免重复类型的方法:

std::numeric_limits< your_type >::max()

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

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


-6
投票

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

例如,在大多数机器中,整数是2个字节= 16位最大值它可以容纳2 ^ 16-1 = 65535 2 ^ 16 = 65536

0%65536 = 0 -1%65536 = 65535,它们应对1111 ............. 1并且所有位都设置为1(如果我们考虑残差类mod 65536)因此它很多直截了当。

我猜

不,如果你认为这个概念对于无符号的整数是完全用餐的,它实际上是有效的

只需检查以下程序片段

int main(){

custom templated function

}

答案为b = 4294967295,即4字节整数为-1%2 ^ 32

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

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


24
投票

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


17
投票

避免上述问题的一种方法是简单地做:

unsigned int flags = 0;
flags = ~flags;

便携式和重点。


13
投票

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

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


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

我认为正确和明显优于简单正确。


9
投票

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

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

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


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;

5
投票

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

unsigned int flags = UINT_MAX;

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

unsigned long flags = ULONG_MAX;

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

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.