在 C 中切换 unsigned int 的给定范围的位

Toggle a given range of bits of an unsigned int in C

我正在尝试替换以下代码

// code version 1
unsigned int time_stx = 11; // given range start
unsigned int time_enx = 19; // given range end
unsigned int time     = 0;  // desired output

while(time_stx < time_enx) time |= (1 << time_stx++);

下面一个没有循环

// code version 2
unsigned int time_stx = 11;
unsigned int time_enx = 19;
unsigned int time     = (1 << time_enx) - (1 << time_stx);

原来在代码版本1中,time = 522240;在代码版本2中,time = 0;当我使用

printf("%u\n", time);

比较结果。我想知道为什么会发生这种情况,以及是否有更快的方法来切换给定范围内的位。我的编译器是 gcc (Debian 4.9.2-10) 4.9.2.

编辑:

感谢您的回复。我犯了一个愚蠢的错误,在没有进一步检查我的代码的情况下发布我的问题让我感到尴尬。我做到了

unsigned int time_stx = 11;
unsigned int time_enx = 19;

unsigned int time1    = 0;
while(time_stx < time_enx) time1 |= (1 << time_stx++); // version 1

//// what I should, but forgotten to do
// time_stx = 11;
// time_enx = 19;

// where time_stx = time_enx now...
unsigned int time2    = (1 << time_enx) - (1 << time_stx); // version 2

// then obviously
printf("time1 = %u\n", time1); // time1 = 522240
printf("time2 = %u\n", time2); // time2 = 0

对于给您带来的不便,我们深表歉意。

备注:time_stxtime_enx都是在运行时期产生的,并不固定。

按照提示是我弄错了,现在问题解决了。谢谢!!

阅读Bit twiddling hacks。即使答案不在那里,您也会在位操作方面得到更好的教育。此外,原始代码只是简单地设置范围内的位;切换意味着将 1 位变为 0 位,反之亦然(通常使用 ^ 或 xor 实现)。

关于代码,我将表达式的三个变体转换为以下 C 代码:

#include <stdio.h>

static void print(unsigned int v)
{
    printf("0x%.8X = %u\n", v, v);
}

static void bit_setter1(void)
{
    unsigned int time_stx = 11; // given range start
    unsigned int time_enx = 19; // given range end
    unsigned int time     = 0;  // desired output

    while (time_stx < time_enx)
        time |= (1 << time_stx++);

    print(time);
}

static void bit_setter2(void)
{
    unsigned int time_stx = 11;
    unsigned int time_enx = 19;
    unsigned int time     = (1 << time_enx) - (1 << time_stx);
    print(time);
}

static void bit_setter3(void)
{
    unsigned int time = 0xFF << 11;
    print(time);
}

int main(void)
{
    bit_setter1();
    bit_setter2();
    bit_setter3();
    return 0;
}

当我查看它的汇编程序时(Mac OS X 10.10.3 上的 GCC 5.1.0),我得到:

        .globl _main
_main:
LFB5:
LM1:
LVL0:
        subq    , %rsp
LCFI0:
LBB28:
LBB29:
LBB30:
LBB31:
LM2:
        movl    2240, %edx
        movl    2240, %esi
        leaq    LC0(%rip), %rdi
        xorl    %eax, %eax
        call    _printf
LVL1:
LBE31:
LBE30:
LBE29:
LBE28:
LBB32:
LBB33:
LBB34:
LBB35:
        movl    2240, %edx
        movl    2240, %esi
        xorl    %eax, %eax
        leaq    LC0(%rip), %rdi
        call    _printf
LVL2:
LBE35:
LBE34:
LBE33:
LBE32:
LBB36:
LBB37:
LBB38:
LBB39:
        movl    2240, %edx
        movl    2240, %esi
        xorl    %eax, %eax
        leaq    LC0(%rip), %rdi
        call    _printf
LVL3:
LBE39:
LBE38:
LBE37:
LBE36:
LM3:
        xorl    %eax, %eax
        addq    , %rsp
LCFI1:
        ret

这是一个惊人的大标签集合!

编译器已完全评估所有三个最小 bit_setterN() 函数并将它们与对 print 的调用一起内联到 main() 的主体中。这包括每次将表达式计算为 522240。

编译器擅长优化。编写清晰的代码并让他们去做,他们会比你优化得更好。显然,如果 11 和 19 在您的代码中没有固定(它们是某种计算变量,可以在运行时变化),那么预计算就不会那么容易(并且 bit_setter3() 是一个非启动器) .然后非循环代码将正常工作,循环代码也是如此。

作为记录,输出为:

0x0007F800 = 522240
0x0007F800 = 522240
0x0007F800 = 522240

如果您的 Debian 编译器从其中一个代码片段中给您一个零,那么您编译的内容和您发布的内容之间可能存在差异,或者编译器中存在错误。总的来说,并没有不尊重的意思,您犯错的可能性比编译器中的错误更可能出现在代码中,就像这样简单。