定义SOMETHING为(1 << 0)。

15

我遇到了这行代码:

#define CPARSER_FLAGS_DEBUG        (1 << 0)

它是做什么的?与此相同:

#define CPARSER_FLAGS_DEBUG        (1)

对吗?


是的,(1 << 0) == (1)。实际上,(i << 0) 优化为 i 或者 (i >> 0) 优化为 i,就像 (i | i) 一样被称为非表达式。 - Grijesh Chauhan
这是同样的事情,就像 1 + 0 等于 1。然而,以这种方式编写代码是为了向读者表明该值旨在用作位掩码(最低有效位为1,其余为0),而不是整数。 - Bogdan Alexandru
3个回答

17

是的,它可能用于在设置标志值时保持对称性:

#define FLAG_1  (1 << 0)
#define FLAG_2  (1 << 2)
#define FLAG_3  (1 << 3)
/* ... */

不必担心性能问题,良好的编译器可以优化此类操作。 您可以按以下方式组合这些值:
/* Flags FLAG_1, FLAG_2 and FLAG_3 are set. */
f = FLAG_1 | FLAG_2 | FLAG_3;

测试一个给定的标志是否被设置:

/* True if FLAG_1 is set. */
if (f & FLAG_1) { /* ... */ }

7
我认为即使是一款极差的编译器也可以对此进行优化。 - Edward Thomson
这里优化起来似乎需要更多的工作。反正这只是一个预处理指令,直接将其转换为0移位的一条指令难道不比积极优化更有效吗? - Phoshi

14
在受C启发的编程语言中,<<>>运算符是左移和右移的位运算符(尽管在C ++中它们可以被重载-最著名的重载可能是I / O流运算符)。例如,
x = y << 2;

将y左移两位的结果赋给x。

通常在低级代码中会看到很多字节移位,原因如下...任何硬件都提供一种配置和控制其行为的方式,但没有人想使用整数来表示开/关状态。因此,硬件开发人员通常提供单个整数(也称为寄存器,通常是无符号32位),并声明例如第0位启用或禁用数据传输,第1位启用或禁用数据过滤,第3位执行其他魔法等等。通常,可以同时读取或更改一个或多个设置。现在想象一下,这对软件开发人员有多方便-与简单的整数(或布尔值)不同,程序员必须处理通常不能由CPU寻址的位。为了简化他们的生活,开发人员定义掩码。坚持以上例子,配置掩码可能如下:

#define MYDEV_ENABLE_DATA_FLOW (1u<<0)
#define MYDEV_ENABLE_FILTERING (1u<<1)
#define MYDEV_ENABLE_MAGIC     (1u<<2)

由于移位表达式的右侧已知,编译器将分别为每个值生成以下整数:

  • 1
  • 2
  • 4

一开始可能不太有意义,但如果您以二进制表示查看这些值,则如下所示:

  • 0b001
  • 0b010
  • 0b100

换句话说,每个值仅在不同位置上设置了一个位。然后,假设我们想要为我们的虚拟设备启用数据传输和魔术功能,但不启用过滤。 为此,我们只需要设置第#0和#2位(1),并取消设置第#1位(0)。 这是当按位或运算符与我们预定义的掩码一起使用时非常方便的地方。 我们这样做:

uint32_t value_for_device = MYDEV_ENABLE_DATA_FLOW | MYDEV_ENABLE_MAGIC;

以下是需要翻译的内容:

0b0010b100 进行 "OR" 运算,得到 0b101 值。我们将此值发送到设备,它会检查每个位并启用或禁用相应的功能。

其他位运算也经常被使用。例如,假设我们不知道当前启用或禁用了哪些功能,但只想确保数据过滤已关闭。可以通过读取当前配置、取消设置该位并将其写回来来实现。例如:

uint32_t value;
value = read_from_device();
value &= (~MYDEV_ENABLE_FILTERING);
write_to_device(value);

这当然不是唯一的用法。查看"Bit Twiddling Hacks" by Sean Eron Anderson,可以找到很多有用的例子。
好的,回到你最初的问题——为什么要写(1<<0)而不是简单地写(1)?有很多原因:

一致性

这样做更加一致:
#define A (1<<0)
#define B (1<<1)
#define C (1<<2)

不是这样:

#define A (1)
#define B (1<<1)
#define C (1<<2)

甚至是这个:

或者是这个:

#define A 1
#define B 2
#define C 4

可维护性
易于更改事物
(保留HTML标记)

It makes it easier to change things around. It is easier to change things around by only changing the shift width and not keep adding/removing '<

Expression of intent

It clearly states the intent of the author to those who read the code. Just 1 does not mean much. But when you see 1<<0 you most likely assume that bit shifting is involved and the code works with bit-masks.

Flexibility

Imagine that the number by which the shift should be performed is defined as a macro. For example:

#define MY_BIT (1u<<MAGIC_BIT_OFFSET)

Then, you don't really know wether the result is 1 or not. And you can keep bit offset definitions separately.

There is probably some more reasons to do this that don't come to my mind straight away.

Hope it clears up things a bit. Good Luck!


2
+1个好答案。但是(iu<<0) == (i<<0),而(-1u>>0) != (-1 >> 0) - Grijesh Chauhan
1
@GrijeshChauhan:没错,如果混合使用有符号算法,可能会出现问题 :) - user405725

10

通常这样做是为了表示定义代表一个位标志,特别是当有多个标志一起定义时。在这种情况下,移位的大小定义了该定义所代表的位位置。像这样定义也可以使事情很好地对齐:

#define FOO_FLAG (1 << 0)
#define BAR_FLAG (1 << 1)
#define BAZ_FLAG (1 << 2)

这可以在调用期望一系列标志的函数时使用,如下所示:
int ret = some_function(FOO_FLAG | BAR_FLAG) & BAZ_FLAG;

这将使用二进制位01(FOO&BAR)调用函数,并检查返回值以查看是否设置了位2(BAZ)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接