我的教授给我们分配了一些使用位字段的作业,并给了我们三个宏
# 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)并在该索引处设置/获取/清除一点。我得到了这个,我得到了如何使用它。
我想知道的是每个宏的确如何。有人可以向我解释这个,我五岁吗?
答案 0 :(得分:2)
忽略下一节中概述的问题,宏将整数类型的数组视为8位值数组,当被要求处理位k
时,处理k%8
数组的k/8
th 元素的 th 位。
但是,它不是使用k % 8
或k / 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 >> 3
将值右移3位,实际除以8。k & 07
提取3个最低有效位(因为07
八进制或7
十进制为111
二进制),忽略其余部分。 01 << (k & 07)
将值1左移0..7位,具体取决于k & 07
的值,产生一个二进制值:
0000 0001
0000 0010
0000 0100
0000 1000
0001 0000
0010 0000
0100 0000
1000 0000
形式上,它实际上是一个int
值,因此可能有32位,但高位位都是零。
~
运算符将每个0位转换为1,每1位转换为0。
&
运算符组合了两个值,产生1位,其中两位都是1,而0是其中一位或两位都是0。|
运算符组合了两个值,产生一个0位,其中两个位都是0和一个1,其中一个或两个位都是1. |=
和&=
将RHS上的操作数应用于LHS上的变量。符号a |= b;
等同于a = a | b;
,但a
仅评估一次。这个细节在这里无关紧要;如果表达式a
中存在增量或类似内容,则会非常重要。全部放在一起:
SETBIT
在由k
表示的8位值数组中设置A
th 位(表示将其设置为1)。CLRBIT
在由k
表示的8位值数组中重置A
th 位(意思是将其设置为0)。GETBIT
在由k
表示的8位值数组中找到A
th 位中的值,并将其作为{{}返回1}}或0
- 这就是最终1
的作用。名义上,数组元素应该是>> (k & 07)
以避免值和空间浪费的问题,但可以使用任何整数类型,或多或少浪费。如果类型为unsigned char
并且在值上设置了高位,或者类型为普通signed char
且普通char
是带符号类型,则会得到有趣的结果。如果char
的类型是大于GETBIT
的整数类型并且数组中的值在最后(最低有效)8位之外设置,您也可以从A
获得有趣的结果数字。
教授提供的宏是如何不编写C预处理器宏的对象课程。他们不教你如何写好C;他们教如何写出令人震惊的可怕的C。
这些宏中的每一个都被危险地破坏了,因为参数char
在使用时没有包含在括号中。不难说这同样适用于k
。使用A
和01
并非完全错误,但八进制07
和01
与十进制07
和1
相同。
7
宏在整个身体周围需要额外的括号。给定
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;
CLRBIT和SETBIT宏使用大括号,这意味着你不能写:
int x = GETBIT(3 + array, y++) + 13;
因为if (GETBIT(array, 13))
SETBIT(array, 27);
else
CLRBIT(array, 19);
之后的分号是SETBIT
引入的语句块中的闭括号之后的空语句,所以SETBIT
子句在语法上是不正确的。
宏可以像这样编写(保留else
和SETBIT
宏的语句块结构):
CLRBIT
#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
进行编译时,会出现令人反感的警告数(对我来说是错误的,因为我使用GCC并使用-DMODE=1
进行编译,这会对错误发出警告)。
-Werror