当前位置:首页 > 公众号精选 > 嵌入式大杂烩
[导读]每天一点C / 位和字节 正文目录: 1. 位相关的运算符 2. 位相关的用法 3. 位字段 (bit field) 4. 怎样判断机器的字节顺序? 5. 怎样将整数转换到二进制或十六进制? 6. 怎样高效地统计整数中为1的位的个数? 7. 相关参考 写作目的: 记录一些 C 语言中位和字


每天一点C / 位和字节

正文目录:

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 中相应的位。

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的位的个数。

相关参考

  • 《C Primer Plus 6th》, 15
  • 《你必须知道的 495 个 C语言问题》, 20.7
  • 《C 和指针》, 5.1.3
  • 《C 专家》, NULL
  • 《C 和 C++ 程序员面试秘籍》, 5
  • 《C 语言解惑》, NULL

思考技术,也要思考人生

学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,想和更多人互相交流学习,关注公众号:嵌入式Hacker,一起来学习吧。

关注 / 转发 / 打赏,都是对作者莫大的支持。觉得文章对你有价值的话,不妨点个 在看和点赞 哦。祝工作顺利,家庭幸福,财源滚滚~

猜你喜欢

C语言对象编程第一弹:封装与抽象

请写一个函数输出如下波形

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

嵌入式开发作为信息技术领域的重要分支,其涉及的语言种类繁多,各具特色。这些语言的选择取决于目标平台的特性、性能需求、开发者的熟练程度以及项目的具体要求。本文将详细介绍几种常见的嵌入式开发语言,包括C语言、C++、汇编语言...

关键字: 嵌入式开发 C语言

Java语言和C语言是两种不同的编程语言,它们在语法、特性和应用领域上有许多差别。下面将详细介绍Java语言和C语言之间的差异以及它们各自的技术特点。

关键字: Java语言 C语言 编程

嵌入式系统是一种专门设计用于特定应用领域的计算机系统,它通常由硬件和软件组成,并且被嵌入到其他设备或系统中,以实现特定的功能。在嵌入式系统的开发过程中,选择适合的编程语言是至关重要的。C语言是一种被广泛应用于嵌入式系统开...

关键字: 嵌入式 计算机 C语言

C语言是一种广泛应用于软件开发领域的编程语言。它是由贝尔实验室的Dennis Ritchie在20世纪70年代初创建的,旨在为UNIX操作系统的开发提供一种高级编程语言。C语言具有简洁、高效、可移植性强等特点,因此成为了...

关键字: C语言 操作系统 应用程序

嵌入式系统是现代生活中无处不在的一部分。它们包括了我们的家电、汽车、智能手机、医疗设备等等。这些系统的工作必须高效、可靠,因为它们往往控制着生活中的关键方面。而C语言作为一种广泛用于嵌入式系统开发的编程语言,其质量和稳定...

关键字: 嵌入式系统 C语言 编程

在嵌入式系统开发领域中,C语言是使用最广泛的编程语言之一。它具有高效、灵活和可移植的特点,成为嵌入式系统设计师的首选语言。本文将介绍C语言编程的基本概念、特点以及在嵌入式系统开发中的应用。

关键字: 嵌入式系统 C语言 编程

C语言编译器是一种用于将C语言源代码转换为可执行程序的软件工具。它的主要功能是将C语言代码翻译成机器语言,以便计算机能够理解和执行。C语言编译器通常包括预处理器、编译器、汇编器和链接器等多个组件,它们协同工作以完成编译过...

关键字: C语言 编译器 Microsoft Visual C++

Matlab和C语言的区别是:1、用途不同;2、语法不同;3、运行速度不同;4、可移植性不同;5、代码管理不同。Matlab是一种数值计算和科学计算工具

关键字: matlab语言 C语言 系统编程

单片机是一种集成电路,它包含了中央处理器、存储器、输入输出接口和时钟等基本部件。单片机广泛应用于各种电子设备中,如家用电器、汽车电子、医疗设备等。单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用...

关键字: 单片机编程 单片机 C语言

一直以来,嵌入式都是大家的关注焦点之一。因此针对大家的兴趣点所在,小编将为大家带来嵌入式的相关介绍,详细内容请看下文。

关键字: 嵌入式 C语言
关闭
关闭