【问题标题】:define SOMETHING (1 << 0)定义一些东西(1 << 0)
【发布时间】:2013-02-26 17:23:18
【问题描述】:

我遇到了这行代码:

#define CPARSER_FLAGS_DEBUG        (1 << 0)

它有什么作用?与以下相同:

#define CPARSER_FLAGS_DEBUG        (1)

对吗?

【问题讨论】:

  • 是的(1 &lt;&lt; 0) == (1)。其实(i &lt;&lt; 0) optimized to i(i &gt;&gt; 0) optimized to ilike ( i | i ) optimized to i. something called not an expression.
  • 是一样的,就像1 + 0等于1。然而,它以这种方式编写是为了向读者表明该值旨在用作位掩码(lsb 为 1,其余为 0)而不是整数。

标签: c


【解决方案1】:

是的,是的。可能在设置标志值时用于对称:

#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) { /* ... */ }

【讨论】:

  • 我认为即使是 abysmal 编译器也可以优化它。
  • 这里似乎需要更多的努力来优化它。无论如何,它是一个预处理器指令,不只是执行一条指令来对其进行 0 移位比积极尝试优化它更有效吗?
【解决方案2】:

在受 C 启发的语言中,&lt;&lt;&gt;&gt; 运算符是左右 bitwise shift 运算符(尽管在 C++ 中它们可以被重载——最著名的重载可能是 I/O stream operators)。例如,

x = y << 2;

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

通常你会在低级代码中看到很多字节移位,这就是为什么......任何硬件都提供了一种配置和控制其行为的方法,但没有人愿意使用整个整数来表示,比如说,ON/关闭状态。因此,硬件开发人员通常提供单个整数(也称为寄存器,通常是无符号 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)。这是 Bitwise OR 运算符以及我们预定义的掩码派上用场的时候。我们要做的是:

uint32_t value_for_device = MYDEV_ENABLE_DATA_FLOW | MYDEV_ENABLE_MAGIC;

0b0010b100 的“或”,给出 0b101 值。我们将其发送到设备,它会检查每一位并启用或禁用相应的功能。

也经常使用其他位操作。比如说,我们不知道当前启用或禁用了什么,我们不想更改任何内容,只需确保关闭数据过滤即可。这可以通过读取当前配置、取消设置位并将其写回来完成。例如:

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

当然,这不是唯一的用法。很多有用的例子,见"Bit Twiddling Hacks" by Sean Eron Anderson

好的,回到你最初的问题——为什么写(1&lt;&lt;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

可维护性

容易改变周围的东西

它使改变周围的事物变得更容易。通过仅更改移位宽度而不是继续添加/删除 '

意图表达

它向阅读代码的人清楚地说明了作者的意图。只是1 意义不大。但是当您看到1&lt;&lt;0 时,您很可能会认为涉及到位移并且代码使用位掩码。

灵活性

想象一下,应该执行移位的数字被定义为一个宏。例如:

#define MY_BIT (1u<<MAGIC_BIT_OFFSET)

那么,你真的不知道结果是不是1。并且您可以单独保留位偏移定义。

我可能没有立即想到这样做的更多理由。

希望它能解决一些问题。祝你好运!

【讨论】:

  • +1 不错的答案。但是(iu&lt;&lt;0) == (i&lt;&lt;0) 其中(-1u&gt;&gt;0) != (-1 &gt;&gt; 0)
  • @GrijeshChauhan: 对.. 如果有符号算术混合使用,可能会出错:)
【解决方案3】:

这通常是为了表明定义代表一个位标志。特别是。当有多个标志一起定义时。在这种情况下,移位的幅度定义了定义所代表的位位置。像这样定义它也能让事情很好地排列起来:

#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)是否已设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-06
    • 2017-10-14
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    相关资源
    最近更新 更多