你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

一些 C 语言中位和字节相关的操作

[复制链接]
gaosmile 发布时间:2020-7-31 21:27

正文目录:

1. 位相关的运算符
2. 位相关的用法
3. 位字段 (bit field)
4. 怎样判断机器的字节顺序?
5. 怎样将整数转换到二进制或十六进制?
6. 怎样高效地统计整数中为1的位的个数?
7. 相关参考

写作目的:

  • 记录一些 C 语言中位和字节相关的操作。

测试环境:

  • Ubuntu 16.04
  • gcc version 5.4.0
1. 位相关的运算符

1) 取反:~

~(10011010) =  (01100101)

运算符 ~ 把 1 变为 0,把 0 变为 1。

2) 按位与:&

(10010011) & (00111101) = (00010001)

运算符 & 通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为 1,结果才为 1。

3) 按位或:|

(10010011) | (00111101) = (10111111)

运算符 | 通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中有 >=1 的位为 1,结果就为 1。

4) 按位异或:^

(10010011) ^ (00111101) = (10101110)

运算符 ^ 逐位比较两个运算对象。对于每个位,如果两个运算对象中有且只有 1 位 为 1, 结果为 1。

5) 左移:<<

(10001010) << 2 = (00101000)

运算符 << 将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值会被丢弃,用 0 填充空出的位置

6) 右移:>>

(10001010) >> 2 = (00100010) // 情况1
(10001010) >> 2 = (11100010) // 情况2

运算符 >> 将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢。

对于无符号类型,用 0 填充空出的位置。

对于有符号类型,其结果取决于机器。空出的位置可能用 0 填充,也可能用符号位填充。


2. 位相关的用法

1) 什么是掩码?

所谓掩码指的是一些设置为开 (1) 或关 (0) 的位组合

为什么叫掩码?看下面这个例子:

#define MASK (1<<1)
flags = flags & MASK;

上面这个例子中,只有 MASK 中 为1的位才可见,掩码中的 0 隐藏 (掩盖) 了 flags 中相应的位。

微信图片_20200731212540.png
mask.png

2) 打开 (设置) 位
有时,比如在操作硬件寄存器的情况下,需要打开一个值中的特定位,同时保持其他位不变。这种情况可以使用按位或运算符 | 和一个掩码进行配合:

#define MASK (1<<1)
flags |= MASK;

3) 关闭 (清空) 位
在不影响其他位的情况下关闭指定的位:

#define MASK (1<<1)
flags &= ~MASK;

4) 切换位
切换位指的是打开已关闭的位,或关闭已打开的位:

#define MASK (1<<1)
flags ^= MASK;

5) 检查位
检查某位的值是否为 1:

#define MASK (1<<1)
(flags & MASK) == MASK

注意,掩码至少要与其覆盖的值的宽度相同,要避免符号位带来的意外,最好在代码中使用 unsigned int 操作位和字节。

6) 提取位

移位运算符可用于从较大单元中提取一些位,例如提取 RBG 颜色值:

#define BYTE_MASK 0xff
unsigned long color = 0x123456;
unsigned char blue, green, red;
red = color & BYTE_MASK;
green = (color >> 8) & BYTE_MASK;
blue = (color >> 16) & BYTE_MASK;


3. 位字段 ( bit field )

位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度,在 Linux 驱动中,某些代码使用了位字段:

struct ap_queue_status {
unsigned int queue_empty : 1;
    ...
unsigned int response_code : 8;
unsigned int pad2  : 16;
} aqs;

给字段赋值:

aqs.queue_empty = 0;
aqs.response_code = 0xff;

所赋的值不能超出字段可容纳的范围。

位字段占用的空间:

struct {
    unsigned int autfd : 1;
    unsigned int bldfc : 1;
    unsigned int undln : 1;
    unsigned int itals : 1;
} prnt;

struct {
    unsigned int code1 : 2;
    unsigned int code2 : 2;
    unsigned int code3 : 6;
    unsigned int code4 : 8;
#if TEST
    unsigned int code5 : 10;
    unsigned int code6 : 12;
    unsigned int code7 : 24;
#endif
} prcode;

int main(void)
{
    printf("%ld %ld\n", sizeof(prnt), sizeof(prcode));
}

测试结果:

4 4     // without TEST
4 12    // with TEST

系统会自动判断出需要几个 byte 的空间来存储数据,在我的机器上测试,一个成员最起码占用 1 个 byte。

位字段的储存顺序:
取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植,我不要求自己写,但是要求自己会看


4. 怎样判断机器的字节顺序?

演示 demo:

int main(void)
{
    int x = 1;
   
    if (*((char *)&x) == 1)
        printf("little - endian\n");
    else
        printf("big - endian\n");

    return 0;
}

运行效果:

$ gcc byte_order.c -o byte_order
$ ./byte_order
little - endian

代码解析:

  • 先初始化在内存中占用 4 个字节的 int 变量。

  • 然后获取int 变量中第 1 个字节的地址,等效代码是:char *px = (char *)&x。

  • 最后获取第 1 个字节的值:*px,观察 *px 是否为 1 就可以知道大小端了。



5. 怎样将整数转换到二进制或十六进制?

演示 demo:

进行任意进制数转换的小函数:

#define BUF_SIZE (33)
char *baseconv(unsigned int num,int base)
{
    static char retbuf[BUF_SIZE];
    char *p;
   
    ...

    p = &retbuf[sizeof(retbuf)-1];
    *p='\0';

    do {
        *--p="0123456789abcdef"[num % base];
        num /=base;
    } while(num !=0);

    return p;
}

在 main() 中进行测试:

int main(void)
{
    int a = 20;

    printf("%s\n", baseconv(a, 2));
    printf("%s\n", baseconv(a, 16));
   
    return 0;
}

运行效果:

$ gcc int_conv.c -o int_conv
$ ./int_conv
10100
14

代码解析:

  • 首先需要明确的是:整数本来就是以二进制存储的,这里说的转换只是指打印的形式

  • 在baseconv() 中的缓冲是 static 的,这有2 个作用:1) 将缓冲清 0,2) 只有是 static 的缓冲才能在函数外部被使用

  • 注意 char *p = &retbuf[sizeof(retbuf)-1] = '\0' 这个操作,这里将缓冲的最高位设置为字符串结束符,同时表明了字符串是从高地址向底地址构造的,函数返回缓冲中有效数据的起始地址

  • 如果你这样打印:

    printf("%d %s %s\n", a, baseconv(a, 2), baseconv(a, 16));

    会得到这样的结果:10100 00。

    这是因为 baseconv() 中的缓冲是 static 的, baseconv(a, 2) 将 baseconv(a, 16) 冲刷掉了



6. 怎样高效地统计整数中为1的位的个数?

演示 demo:

统计整数中为1的位的个数的小函数:

static int bitcounts[] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};

int bitcount(unsigned int u)
{
    int n=0;

    for(; u!=0; u>>=4)
        n += bitcounts[u & 0x0f];

    return n;
}

在 main() 中进行测试:

int main(void)
{
    int i = 0;

    for (i=0; i<=0x0f; i++)
        printf("%d\n", bitcount(i));

    return 0;
}

运行效果:

$ gcc bit_counts.c -o bit_counts
$ ./bit_counts
0
1
1
2
1
2
2
3
1
2
2
3
2
3
3
4

代码解析:

  • 许多像这样的位问题可以使用查找表格来提高效率和速度。

  • 这段代码是以每次 4 位的方式计算数值中为1的位的个数。



收藏 1 评论0 发布时间:2020-7-31 21:27

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版