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


132

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

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

这是完成此任务的一种好方法吗?还是使用0xffffffff~0更佳?


1
我不明白,您能解释一下吗?
corazza

8
我认为代码的含义是否清晰是最重要的问题。尽管-1始终可以使用,但在注释后需要注释这一事实表明它不是清晰的代码。如果该变量是标志的集合,为什么还要为其分配一个整数?它的类型可以是整数,但在语义上肯定不是整数。您永远不会增加或增加它。因此,我不会将其0xffffffff用于可移植性或正确性,而是为了清楚起见。
·杰克逊

@CamJackson注释和任何人编写C代码可能是熟悉的值是如何表示。
Miles Rout

该问题最初被正确标记为C和C ++。语言可能会有所不同,因为C ++提出了要求二进制补码的提议。就是说,这并没有改变-1仍然是两种语言的可移植且向后兼容的解决方案的事实,但是它可能会影响其他答案中的某些推理。
阿德里安·麦卡锡

Answers:


154

我建议您严格按照显示的方法进行操作,因为这是最直接的方法。初始化-1始终起作用,而与实际的符号表示无关,而~有时会出现令人惊讶的行为,因为您必须具有正确的操作数类型。只有这样,您才能获得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为零。

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

我们不是在讨论是否-1所有位都为1(并非总是都有)。而且,我们并不是在说~0所有位是否都为1(当然,它是否全部为1)。

但是,我们所说的是初始化flags变量的结果是什么。因此,-1适用于每种类型和机器。


9
为什么要保证-1转换为全1?标准保证了吗?
jalf

9
发生的转换是,它反复将ULONG_MAX加一,直到达到范围(C TC2草案中的6.3.1.3)。在C ++中是相同的,只是使用另一种形式化它的方式(模2 ^ n)。一切都取决于数学关系。
Johannes Schaub-litb

6
@litb:强制转换-1当然是获得最大无符号值的好方法,但它并不是真正的描述性内容;这就是_MAX常量存在的原因(在C99中添加了SIZE_MAX);理所当然的,C ++版本numeric_limits<size_t>::max()有点冗长,但是强制转换也是如此……
Christoph

11
“我们不是在谈论-1是否所有位都为一(它并不总是有)。我们不是在谈论〜0是否具有所有位为一(当然是具有)。” -??? 我以为重点将所有位都设置为1。这就是标志的工作方式。你看看这些。谁在乎价值?
mpen 2010年

14
@标记发问者在乎。他问“使用-1将所有位设置为true是否安全”。这既不询问-1代表什么位,也不询问~0具有什么位。我们可能不在乎值,但是编译器会在意。我们不能忽略这样的事实,即操作使用值并按值进行操作。该~0可能不是-1,但是这是你所需要的价值。请参阅我的答案和@Dingo的摘要。
Johannes Schaub-litb

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

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


10
〜0(即一个人的补数运算符)如何依赖于两个人的补数表示形式?
德鲁·霍尔

11
你有这个倒退。其设置标志为-1,这取决于二进制补码表示形式。在符号+幅度表示中,减号仅设置两个位:符号位和幅度的最低有效位。
斯蒂芬·斯蒂尔

15
C标准要求int值零具有其符号位,并且所有值位均为零。在补全之后,所有这些位均为1。设置了所有位的int值是:符号和大小:INT_MIN一个补码:-0两个补码:-1因此,语句“ unsigned int flags =〜0;” 将分配与平台的整数表示形式相匹配的上述任何一个值。但是二的补码的“ -1”是将所有标志位都设置为1的唯一补码。
Dingo

9
@Stephen:同意申述。但是,当将一个int值分配给一个无符号的int时,该无符号的值不会通过采用该int值的内部表示形式来获取其值(在通常起作用的二进制补码系统中除外)。分配给无符号int的所有值都是模(UINT_MAX + 1),因此无论内部表示如何,分配-1都可以工作。
Dingo

20
@Mark:您混淆了两个操作。~0当然会产生一个int设置了所有位的值。但是,将an分配int给an unsigned int并不一定会导致unsigned int具有与有符号位模式相同的位模式。通常只有2的补码表示。在1的补码或符号幅度表示中,将负值分配给结果intunsigned int导致不同的位模式。这是因为C ++标准将有符号->无符号转换定义为模等于值,而不是具有相同位的值。
史蒂夫·杰索普

25

坦白地说,我认为所有fff都更具可读性。关于它是反模式的评论,如果您真的在乎所有位都已设置/清除,那么我会认为您可能处于无论如何都在乎变量大小的情况下,这需要使用boost等方法。 :: uint16_t等


在许多情况下,您不太在意,但很少见。例如,算法通过将N位数据集分解为大小为sizeof(unsigned)* CHAR_BIT位的每个块来工作。
MSalters,2009年

2
+1。即使数据类型的大小大于F的个数(即,您没有将所有位都设置为true),由于要显式设置该值,因此您至少要知道哪些位是“安全的”。要使用” ..
MPEN

17

避免出现上述问题的方法是简单地执行以下操作:

unsigned int flags = 0;
flags = ~flags;

轻便且指向重点。


2
但是随后您就失去了声明flags为的能力const
大卫·斯通

1
@DavidStoneunsigned int const flags = ~0u;

@Zoidberg'-不能在除二进制补码之外的系统上工作。例如,在符号级系统上,~0是一个所有位都设置为1的整数,但是当您将其分配intunsigned变量时flags,您执行了从-2**31(假设为32位int)到的值转换(-2**31 % 2**32) == 2**31,该值是一个整数与所有位,但第一套1
大卫·斯通

这也是为什么这个答案很危险的原因。似乎@Zoidberg'-的答案是相同的,但实际上并非如此。但是,作为一个阅读代码的人,我将不得不考虑一下它,以了解为什么您要花两个步骤来初始化它,并且可能很想将其更改为一个步骤。
大卫·斯通

2
嗯,是的,我没有注意到u您答案中的后缀。当然可以,但是仍然存在unsigned两次指定使用的数据类型(且不能更大)的问题,这可能导致错误。但是,如果赋值和初始变量声明相距较远,则该错误最有可能出现。
大卫·斯通

13

我不确定使用无符号int作为标记在C ++中是不是一个好主意。比特集之类的呢?

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


我喜欢它是因为它的标准,但是它太罗word了,它使您两次声明类型。使用〜0可能更安全,因为0可以是任何整数类型。(尽管我知道它闻到了太多的C。)
Macke,

它罗word的事实可以看作是一种优势。但我也喜欢〜0。
爱德华A.

2
您可以使用标准的UINT_MAX宏来缓解冗长的情况,因为无论如何您都要对unsigned int类型进行硬编码。

2
@Macke您可以避免使用来声明C ++ 11中的类型autoauto const flags = std::numeric_limit<unsigned>::max()
大卫·斯通

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

“这是实现此目的的一种好方法吗?”

随身携带? 是的

好? 值得商bat的,此线程上显示的所有混乱证明了这一点。足够清晰,以使您的同行程序员可以理解代码而不会引起混淆,这应该是我们衡量良好代码的维度之一。

另外,此方法容易产生编译器警告。要消除警告而不损害编译器,您需要显式强制转换。例如,

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

我认为正确和明显比单纯正确更好。


10

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

当然,0xffffffff如果您知道确实需要32位,也可以写出来,但是-1的优点是,即使您不知道类型的大小,它也可以在任何上下文中运行,例如可以在多种类型上使用的宏,或者类型的大小因实现而异。如果你知道的类型,另一种安全的方式来获得所有的人已是极限宏UINT_MAXULONG_MAXULLONG_MAX,等。

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


FWIW,如果我的意思是我使用的“全1位” ~(type)0type当然,请填写右边)。强制转换零仍然会导致零,因此很明显,并且明确定义了目标类型中所有位的取反。我实际上并不经常需要该操作;YMMV。
Donal Fellows 2010年

6
@Donal:你根本是错的。C指定当转换不适合无符号类型的值时,这些值将以2 ^ n为模减少,其中n是目标类型中的位数。这适用于有符号的值和较大的无符号类型。它与二进制补码无关。
R .. GitHub停止帮助ICE,2010年

2
他们确实实现了相同的目标,这是微不足道的。您只需使用无符号的减法操作码,而不是带符号的减法操作码或neg指令。伪造有符号算术行为的机器具有单独的有符号/无符号算术操作码。当然,一个非常好的编译器甚至总是忽略有符号的操作码,即使有符号的值也是如此,从而免费获得二进制补码。
R .. GitHub停止帮助ICE,2010年

1
@R .:如果var = ~(0*var)大小写var比窄,则大小写将失败int。也许var = ~(0U*var)吧?(-1不过我个人还是比较喜欢)。
caf 2010年

1
这个答案比约翰内斯·绍布(Johannes Schaub)的要清楚得多。但是,将负负文字整数分配给无符号类型而不进行强制转换通常会导致编译器警告。取消警告需要强制转换,这意味着无论如何都必须注意目标类型,因此您不妨使用UINT_MAX或ULONG_MAX并保持清晰,而不是依赖于标准中的微小细节,这显然会使许多其他程序员感到困惑。
Adrian McCarthy

5

只要您有#include <limits.h>一个包含项,就应该使用

unsigned int flags = UINT_MAX;

如果您想买多头钱,可以使用

unsigned long flags = ULONG_MAX;

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


1
您建议的常量实际上是在limits.h中定义的-stdint.h包含其他整数类型(固定大小的整数,intptr_t等的限制)
Christoph

5

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

3
作为C程序员,这样的代码在晚上给我带来了噩梦。
jforberg

当在有符号整数ALL_BITS_TRUE ^ a在哪里的上下文中使用它时会发生什么a?类型保持带符号整数,并且位模式(对象表示)取决于目标是否为2的补码。
彼得·科德斯

不,ALL_BITS_TRUE ^ a由于ALL_BITS_TRUE模棱两可,因此会产生编译错误。它可以像一样使用uint32_t(ALL_BITS_TRUE) ^ a。你可以尝试一下自己在cpp.sh :)现在我想补充一个static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");operator,以确保用户不尝试使用int(ALL_BITS_TRUE)。我将更新答案。
钻石巨蟒

3

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

在您的情况下,我始终使用0xFFFF。(当然,对于可变大小,请使用正确数量的Fs。)

[顺便说一句,我很少看到在实际代码中完成-1技巧。

此外,如果你真的关心在vairable的各个位,这将是不错的主意,开始使用固定宽度uint8_tuint16_tuint32_t类型。


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位变量时却遇到了程序失败,而我却得到了0xffffffffffffffffff。在C中,它将是:

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

结果是:

x is 0xffffffffffffffff

我想将其发布为所有回答的注释,这些回答说0xFFFFFFFF假定为32位,但是很多人回答了它,所以我认为我会将其添加为单独的答案。


1
恭喜,您已找到编译器错误!
tc。

是否已在任何地方将其记录为错误?
弥敦道·费尔曼

1
假设UINT64_C(0xffffffff)扩展为0xffffffffuLL,肯定是编译器错误。C标准主要讨论,用表示的值0xffffffff是4294967295(不是36893488147419103231),并且看不到有符号整数类型的任何转换。
tc。

2

有关问题的非常清晰的说明,请参见litb的答案。

我的不同意是,严格来说,这两种情况都不能保证。我不知道有没有一种架构能代表所有位都设置成无符号值“位数小于2的幂”,但这就是标准的实际含义(3.9.1 / 7加注释44):

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

这就使这些位之一完全可能成为任何东西的可能性。


通常,我们不能确定填充位的值。如果我们愿意,那么我们可能会处于危险之中,因为我们可能会为他们生成陷阱表示(并且可能会发出信号)。但是,std要求unsigned char没有填充位,并且在c ++标准中的4.7 / 2中表示将整数转换为unsigned类型,结果所得的unsigned变量的值是与源整数值一致的最小值, (以2 ^ n为模,n ==无符号类型的位数)。然后(-1)==(((2 ^ n)-1)(mod 2 ^ n)。2 ^ n-1具有在纯二进制编号系统中设置的所有位。
约翰尼斯·绍布

如果我们真的想让无符号类型的对象表示中的所有位为1,则需要memset。但是我们可以这样生成陷阱表示:(无论如何,实现可能没有理由丢掉它们的无符号整数,因此它将使用它来存储其值。但是您有一个很好的观点-没有停止的地方了我认为是从一些愚蠢的废话解释出来的(除了char / signed char / unsigned char,一定不能有这些废话)。+1当然是:)
Johannes Schaub-litb

最后,我认为该标准可以更清楚地表示它在4.7 / 2中所指的是什么。如果它涉及对象表示,那么就不再有填充位的地方了(我见过有人在争论那种我认为没有错的地方)。但是我认为它谈论的是值的表示(因为4.7 / 2中的所有内容还是关于值的-然后填充位可能会嵌套在值位的旁边。)
Johannes Schaub-litb

1
标准似乎很清楚地想到了“ 2's补码,1's补码和正负号”表示,但不想排除任何问题。关于捕获表示形式也很有趣。据我所知,就标准而言,我引用的位是“纯二进制计算系统”的定义-最后的“除外”位实际上是我对是否可以保证强制转换-1的唯一疑问工作。
詹姆斯·霍普金

2

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

如果将typedef值用于位掩码,则另一种可能性是使用〜(bitMaskType)0; 如果位掩码恰好是16位类型,则该表达式将只设置16位(即使'int'否则为32位),但由于只需要16位,因此只要一个实际上在类型转换中使用适当的类型。

顺便说一句,longvar &= ~[hex_constant]如果十六进制常数太大而不能放入,则该形式的表达式会令人讨厌int,但是会适合unsigned int。如果an int为16位,则为longvar &= ~0x4000;longvar &= ~0x10000;会清除的一位longvar,但longvar &= ~0x8000;会清除第15位及以上的所有位。合适的值int将对类型应用补数运算符int,但结果将符号扩展为long,从而设置高位。太大的值unsigned int将把补数运算符应用于type long。但是,介于这些大小之间的值会将补数运算符应用于type unsigned int,然后将其转换为long没有符号扩展名的type 。


1

实际上:是的

理论上:不。

-1 = 0xFFFFFFFF(或平台上int的任何大小)仅适用于二进制补码算法。实际上,它可以工作,但是那里有旧式机器(IBM大型机等),在该机器中您具有实际的符号位,而不是二进制补码。您建议的〜0解决方案应该可以在任何地方使用。


6
我也这么说 但是后来我意识到我错了,因为在转换规则下,-1签名始终转换为max_value unsigned,无论值表示形式如何。至少,它在C ++中没有,我没有C标准。
史蒂夫·杰索普

6
有歧义。-1不是0xFFFFFFFF。但是,如果将-1转换为无符号int(具有32位),则为0xFFFFFFFF。我认为这就是使讨论变得如此困难的原因。许多人在谈论这些位字符串时,会想到很多不同的事情。
Johannes Schaub-litb

1

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

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

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


0

这肯定是安全的,因为-1将始终设置所有可用位,但是我更喜欢〜0。-1只是没有太大的意义unsigned int0xFF...不好,因为它取决于类型的宽度。


4
“ 0xFF ...不好,因为它取决于类型的宽度”,我认为这是唯一可行的方法。您应该清楚地定义程序中每个标志/位的含义。所以,如果你确定你正在使用最低的32位存储的标志,你应该限制自己使用这些32位,整型的实际尺寸是32位还是64
胡安·巴勃罗·卡利法诺

0

我说:

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

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


4
不能在具有9位字符的系统上使用!
tc。

0

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

为C和C ++的任何无符号整数类型(无符号int,uint8_t,uint16_t等)分配-1均有效

或者,对于C ++,您可以:

  1. 包含<limits>和使用std::numeric_limits< your_type >::max()
  2. 编写自定义模板函数(这还将允许进行完整性检查,即,如果目标类型确实是未签名的类型)

目的可以更加清晰,因为分配-1总是需要一些解释性的意见。


0

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

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

-6

是的,显示的表示形式非常正确,就像我们以另一种方式进行处理时,您将需要运算符反转所有位,但是在这种情况下,如果我们考虑机器中整数的大小,逻辑将非常简单

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

0%65536 = 0 -1%65536 = 65535对应于1111 ......... 1,并且所有位都设置为1(如果考虑残基类别为65536,则为0)直截了当。

我猜

不,如果您考虑这个概念,那么它是无符号整数的理想选择,它实际上可以解决

只需检查以下程序片段

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

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

如有任何差异,请报告


9
有两条评论:首先,您对“大多数”计算机上的整数大小完全错误。其次,您的txt iz很难2归类到我们两种缩略语。请用普通的英语
康拉德·鲁道夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.