【问题标题】:C: Bit fields and bitwise operatorsC:位域和位运算符
【发布时间】:2015-11-15 03:29:01
【问题描述】:

我的教授给我们布置了一些使用位域的作业,并给了我们三个宏

# define SETBIT(A, k) { A[k >> 3] |= (01 << (k & 07)); }
# define CLRBIT(A, k) { A[k >> 3] &= ~(01 << (k & 07)); }
# define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)

我们不需要/期望完全理解,仅用于完成作业。这些宏中的每一个都采用unsigned int 和索引(0-8)并在该索引处设置/获取/清除位。我知道了,我知道了如何使用它。

我想知道的是这些宏的具体作用。有人可以像我五岁一样向我解释这个吗?

【问题讨论】:

  • 如果您知道&gt;&gt;&lt;&lt; 位移操作的作用,&amp;| 的按位工作原理,并且x op= yx = x op y 的简写,您可以通过一些示例自己解决这个问题。试试 k==7 和 k==8... 提示:A 是一个 8 位字节数组,右移 3 与除以 8 相同,7 的二进制表示为 00000111。

标签: c bit-manipulation bit


【解决方案1】:

宏的作用

忽略下一节中概述的问题,宏将整数类型的数组视为 8 位值的数组,当被要求处理位 k 时,处理 k%8th 位数组的k/8th 元素。

但是,它不是使用k % 8k / 8,而是使用移位和掩码。

# define SETBIT(A, k) { A[k >> 3] |= (01 << (k & 07)); }
# define CLRBIT(A, k) { A[k >> 3] &= ~(01 << (k & 07)); }
# define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
  • k &gt;&gt; 3 将值右移 3 位,实际上是除以 8。
  • k &amp; 07 提取 3 个最低有效位(因为 07 八进制或 7 十进制是 111 二进制),忽略其余部分。
  • 01 &lt;&lt; (k &amp; 07) 根据k &amp; 07 的值将值 1 左移 0..7 位,生成二进制值之一:

    0000 0001
    0000 0010
    0000 0100
    0000 1000
    0001 0000
    0010 0000
    0100 0000
    1000 0000
    

    形式上,它实际上是一个int 值,因此可能有 32 位,但高位全为零。

  • ~ 运算符将每个 0 位转换为 1,将每个 1 位转换为 0。

  • &amp; 运算符将两个值组合在一起,产生一个位为 1 的位和一个位为 0 的位和一个位为 0 的位。
  • | 运算符将两个值组合在一起,产生一个 0 位,其中两个位都为 0,而一个 1,其中一个或两个位都为 1。
  • 赋值运算符|=&amp;= 将RHS 上的操作数应用于LHS 上的变量。符号a |= b; 等价于a = a | b;,只是a 只计算一次。这个细节在这里无关紧要。如果表达式 a 中有增量或类似内容,则非常重要。

把它们放在一起:

  • SETBITA 表示的8 位值数组中设置kth 位(意思是将其设置为1)。
  • CLRBIT 重置 A 表示的 8 位值数组中的 kth 位(意味着将其设置为 0)。
  • GETBITA 表示的 8 位值数组中找到 kth 位中的值,并将其返回为 01 — 这就是最终&gt;&gt; (k &amp; 07) 确实如此。

名义上,数组元素应该是unsigned char 以避免值问题和浪费空间,但可以使用任何整数类型,或多或少浪费。如果类型是 signed char 并且在值上设置了高位,或者类型是普通的 char 并且普通的 char 是有符号类型,那么您会得到有趣的结果。如果A 的类型是大于char 的整数类型,并且数组中的值的位设置在数字的最后(最低有效)8 位之外,您还可以从GETBIT 获得有趣的结果。

宏不能做什么

教授提供的宏是关于如何不编写 C 预处理器宏的对象课程。他们不教你如何写出好的 C;他们教如何编写极其糟糕的 C。

这些宏中的每一个都被危险地破坏了,因为参数k 在使用时没有用括号括起来。不难争辩这同样适用于A0107 的使用并没有完全错误,但是八进制 0107 与十进制 17 相同。

GETBIT 宏也需要在其整个主体周围加上一层额外的括号。给定

int y = 2;
unsigned char array[32] = "abcdefghijklmnopqrstuvwxyz01234";

那么这不会编译:

int x = GETBIT(array + 3, y + 2) + 13;

如果您的编译器选项足够宽松,这确实会编译(带有警告),但会产生奇怪的结果:

int x = GETBIT(3 + array, y + 2) + 13;

这是在我们尝试讨论之前:

int x = GETBIT(3 + array, y++) + 13;

CLRBIT 和 SETBIT 宏使用大括号,这意味着你不能写:

if (GETBIT(array, 13))
    SETBIT(array, 27);
else
    CLRBIT(array, 19);

因为SETBIT 后面的分号是SETBIT 引入的语句块中右大括号后面的空语句,所以else 子句在语法上根本不正确。

宏可以这样写(保留SETBITCLRBIT宏的语句块结构):

#define SETBIT(A, k) do { (A)[(k) >> 3] |= (1 << ((k) & 7)); } while (0)
#define CLRBIT(A, k) do { (A)[(k) >> 3] &= ~(1 << ((k) & 7)); } while (0)
#define GETBIT(A, k) (((A)[(k) >> 3] & (1 << ((k) & 7))) >> ((k) & 7))

do { … } while (0) 表示法是宏中的一种标准技术,可以解决破坏if / else 语句的问题。

宏也可以这样重写,因为赋值是表达式:

#define SETBIT(A, k) ( (A)[(k) >> 3] |=  (1 << ((k) & 7)))
#define CLRBIT(A, k) ( (A)[(k) >> 3] &= ~(1 << ((k) & 7)))
#define GETBIT(A, k) (((A)[(k) >> 3] &   (1 << ((k) & 7))) >> ((k) & 7))

或者,更好的是,static inline 的功能如下:

static inline void SETBIT(unsigned char *A, int k) { A[k >> 3] |=  (1 << (k & 7)); }
static inline void CLRBIT(unsigned char *A, int k) { A[k >> 3] &= ~(1 << (k & 7)); }
static inline int  GETBIT(unsigned char *A, int k) { return (A[k >> 3] & (1 << (k & 7))) >> (k & 7); }

整体可以组装成一个简单的测试程序:

#if MODE == 1

/* As provided */
#define SETBIT(A, k) { A[k >> 3] |= (01 << (k & 07)); }
#define CLRBIT(A, k) { A[k >> 3] &= ~(01 << (k & 07)); }
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)

#elif MODE == 2

/* As rewritten */
#define SETBIT(A, k) do { (A)[(k) >> 3] |= (1 << ((k) & 7)); } while (0)
#define CLRBIT(A, k) do { (A)[(k) >> 3] &= ~(1 << ((k) & 7)); } while (0)
#define GETBIT(A, k) (((A)[(k) >> 3] & (1 << ((k) & 7))) >> ((k) & 7))

#else

/* As rewritten */
static inline void SETBIT(unsigned char *A, int k) { A[k >> 3] |=  (1 << (k & 7)); }
static inline void CLRBIT(unsigned char *A, int k) { A[k >> 3] &= ~(1 << (k & 7)); }
static inline int  GETBIT(unsigned char *A, int k) { return (A[k >> 3] & (1 << (k & 7))) >> (k & 7); }

#endif

int main(void)
{
    int y = 2;
    unsigned char array[32] = "abcdefghijklmnopqrstuvwxyz01234";
    int x = GETBIT(array + 3, y + 2) + 13;
    int z = GETBIT(3 + array, y + 2) + 13;

    if (GETBIT(array, 3))
        SETBIT(array, 22);
    else
        CLRBIT(array, 27);

    return x + z;
}

当使用-DMODE=2-DMODE=0 或没有任何-DMODE 设置编译时,它是干净的。使用-DMODE=1 编译时,会出现令人反感的警告数量(对我来说是错误,因为我使用 GCC 并使用-Werror 编译,这会使任何警告都变成错误)。

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=0 bits23.c -o bits23 
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=2 bits23.c -o bits23
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=1 bits23.c -o bits23
bits23.c: In function ‘main’:
bits23.c:28:33: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
     int x = GETBIT(array + 3, y + 2) + 13;
                                 ^
bits23.c:6:25: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                         ^
bits23.c:6:24: error: subscripted value is neither array nor pointer nor vector
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                        ^
bits23.c:28:13: note: in expansion of macro ‘GETBIT’
     int x = GETBIT(array + 3, y + 2) + 13;
             ^
bits23.c:28:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
     int x = GETBIT(array + 3, y + 2) + 13;
                                 ^
bits23.c:6:43: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                                           ^
bits23.c:28:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
     int x = GETBIT(array + 3, y + 2) + 13;
                                 ^
bits23.c:6:57: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                                                         ^
bits23.c:29:33: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
     int z = GETBIT(3 + array, y + 2) + 13;
                                 ^
bits23.c:6:25: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                         ^
bits23.c:29:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
     int z = GETBIT(3 + array, y + 2) + 13;
                                 ^
bits23.c:6:43: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                                           ^
bits23.c:29:22: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
     int z = GETBIT(3 + array, y + 2) + 13;
                      ^
bits23.c:6:23: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                       ^
bits23.c:29:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
     int z = GETBIT(3 + array, y + 2) + 13;
                                 ^
bits23.c:6:57: note: in definition of macro ‘GETBIT’
 #define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
                                                         ^
bits23.c:29:38: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
     int z = GETBIT(3 + array, y + 2) + 13;
                                      ^
bits23.c:33:5: error: ‘else’ without a previous ‘if’
     else
     ^
cc1: all warnings being treated as errors
$

【讨论】:

  • 非常感谢您提供如此详细的解释,尤其是解释为什么这不是最佳实践以及如何改进它!说真的,希望我能投票两次。
猜你喜欢
  • 2020-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多