《C和指针》5.9 - 5 :
编写函数,把一个给定的值存储到一个整数中指定的几个位。它的原型如下:
int store_bit_field(int original_value,
int value_to_store,
unsigned starting_bit,
unsigned ending_bit);
假定整数中的位是从右向左进行编号。因此,起始位的位置不会小于结束位的位置,为了更清楚的说明,函数应该返回下列值。
原始值 | 需要储存的位 | 起始位 | 结束位 | 返回值 |
0x0 | 0x1 | 4 | 4 | 0x10 |
0xffff | 0x123 | 15 | 4 | 0x123f |
0xffff | 0x123 | 13 | 9 | 0xc7ff |
提示:把一个值存储到一个整数中指定的几个位分为5个步骤,以上表最后一行为例:
1).创建一个掩码,它是一个值,其中需要存储的位置相对应的那几个位设置为1,此时掩码为
00111110,00000000
2).用掩码的反码对原值执行AND操作,将那几个位设置为0.原值1111111111111111,操作后变为
11000001,11111111
3).将新值左移,使它与需要存储的位对齐,新值00000001,00100011(0x123),左移后变为
新值存储在第9-13位,将其每一位左移,直到最低位左移到第9位,保持最低位对齐。
01000110,00000000
4).把移位后的值与掩码进行位AND操作,确保除那几个需要存储的位之外的其余位都设置为0,进行这个操作之后,值变为
00000110,00000000
5).把结果值与原值进行位OR操作,结果为(0xc7ff)
11000111,11111111
在所有任务中,最困难的是创建掩码,你一开始可以把~0这个值强制转换为无符号值,然后再对它进行移位。
根据上面的步骤,可以实现如下代码:
#include <stdio.h>
#include <stdint.h>
/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 \
PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- end macros --- */
void print_binary(unsigned int number) {
printf(PRINTF_BINARY_PATTERN_INT32, PRINTF_BYTE_TO_BINARY_INT32(number));
}
int store_bit_field(int original_value,
int value_to_store,
unsigned starting_bit,
unsigned ending_bit) {
printf("\n\norigin: 0x%0x\n", original_value);
//取得掩码
unsigned mask = 0;
for (int i = starting_bit; i <= ending_bit; i++) {
mask |= (1 << i);
}
printf("Mask:");
print_binary(mask);
printf("\n");
//将目标位置清0
printf("Original value (clear dst bits): ");
unsigned tmp = original_value & ~mask;
print_binary(tmp);
printf("\nNew value (low bits align):");
//将要储存的新值和目标位置对其,低位对齐
value_to_store = value_to_store << starting_bit;
print_binary(value_to_store);
printf("\nNew value (get dst bits with mask):");
//通过掩码取得对齐后的新值
value_to_store &= mask;
print_binary(value_to_store);
printf("\nResult:");
//对齐后的值与原值清除目标位后的值或操作
unsigned result = value_to_store | tmp;
print_binary(mask);
printf("\nresult: 0x%0x\n", result);
print_binary(result);
}
int main () {
store_bit_field(0x0, 0x1, 4, 4);
store_bit_field(0xffff, 0x123, 4, 15);
store_bit_field(0xffff, 0x123, 9, 13);
return 0;
}
输出:
origin: 0x0
Mask:00000000000000000000000000010000
Original value (clear dst bits): 00000000000000000000000000000000
New value (low bits align):00000000000000000000000000010000
New value (get dst bits with mask):00000000000000000000000000010000
Result:00000000000000000000000000010000
result: 0x10
00000000000000000000000000010000
origin: 0xffff
Mask:00000000000000001111111111110000
Original value (clear dst bits): 00000000000000000000000000001111
New value (low bits align):00000000000000000001001000110000
New value (get dst bits with mask):00000000000000000001001000110000
Result:00000000000000001111111111110000
result: 0x123f
00000000000000000001001000111111
origin: 0xffff
Mask:00000000000000000011111000000000
Original value (clear dst bits): 00000000000000001100000111111111
New value (low bits align):00000000000000100100011000000000
New value (get dst bits with mask):00000000000000000000011000000000
Result:00000000000000000011111000000000
result: 0xc7ff
00000000000000001100011111111111
这个问题可以很好的帮助我们理解数据的二进制存储结构和位运算,书上给出了步骤,如果没有给这个算法步骤,我们也需要能够想到:
1、得到目标位置的掩码。
2、根据掩码取反再与原值相与,清空目标位置的数据。
3、新的值要存入目标位,但由于目标位的长度不确定,这里只能低位对齐,保证尽可能存入低位,所以需要左移到需要对齐的位置,这里我直接将编号和大小调整为一致了,也可以根据题目将starting_bit和ending_bit交换。
4、利用掩码取出新值的目标位置区间的值。
5、将4的结果与2的结果进行或运算即可得到最终的值。