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

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

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

正文目录:

1. 位相关的运算符) y! J) M- M. ]5 Z6 H
2. 位相关的用法! w+ |9 D" v) k5 w
3. 位字段 (bit field); l/ M" \8 F, H0 @+ f7 N: O0 A
4. 怎样判断机器的字节顺序?: u+ w- @2 w. P+ I' e
5. 怎样将整数转换到二进制或十六进制?3 T2 `  k& c5 u( k% c5 t
6. 怎样高效地统计整数中为1的位的个数?
5 I9 z- v; }) K: N( C5 Q1 I( N3 t' v( }7. 相关参考

写作目的:

  • 记录一些 C 语言中位和字节相关的操作。# U8 ]- z7 b! w) Z, x

测试环境:

  • Ubuntu 16.04
  • gcc version 5.4.05 f% a" u4 F5 W
1. 位相关的运算符

1) 取反:~

~(10011010) =  (01100101)8 X- H6 p: P( G" I- S

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

2) 按位与:&

(10010011) & (00111101) = (00010001)# ], V( n! y. [' U

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

3) 按位或:|

(10010011) | (00111101) = (10111111)+ P* N9 K' I- ]6 C7 H9 T/ u0 s; I

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

4) 按位异或:^

(10010011) ^ (00111101) = (10101110)  E$ K2 H  G' D5 E7 }- i

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

5) 左移:<<

(10001010) << 2 = (00101000)& E% ^8 L% R  @0 y9 g- k# J

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

6) 右移:>>

(10001010) >> 2 = (00100010) // 情况1
4 |5 U1 e/ ~: [4 E6 z(10001010) >> 2 = (11100010) // 情况2

; J5 k. |4 o% R9 {4 k5 n5 V

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

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

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


2. 位相关的用法

1) 什么是掩码?

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

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

#define MASK (1<<1)# u1 {, I" f/ h4 C- ?% Q
flags = flags & MASK;
2 k5 e+ L5 U' x7 X, S2 d

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

微信图片_20200731212540.png
mask.png# ^: j+ E' t2 }; [* e

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

#define MASK (1<<1)0 f, a7 E5 N- t% _
flags |= MASK;
! [2 S% T2 C( Y2 g+ u" q/ ?3 s

3) 关闭 (清空) 位: D) R+ |4 D7 R2 d; p
在不影响其他位的情况下关闭指定的位:

#define MASK (1<<1)" b; r" ^9 Y8 v9 f- J
flags &= ~MASK;

+ u0 C! N7 }/ f# S) o' V

4) 切换位
) ?  }0 [) }( I! i* ^( e* w! f切换位指的是打开已关闭的位,或关闭已打开的位:

#define MASK (1<<1)( A% j4 x# k. I
flags ^= MASK;
, u6 \$ f6 N  f1 s1 k% {

5) 检查位
, }% c2 o6 M* v! X9 B% E/ s& I检查某位的值是否为 1:

#define MASK (1<<1)
: N: Y' U" s, [% o' V(flags & MASK) == MASK
* a9 h8 Q2 F& ~) Q* r1 R

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

6) 提取位

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

#define BYTE_MASK 0xff
- p6 L7 `! S8 s& P, b+ h( Gunsigned long color = 0x123456;8 Q% D% C4 n. _- z8 k
unsigned char blue, green, red;! v6 C) Z9 m* k' g* Z. ~' Z' I
red = color & BYTE_MASK;
+ |: m/ Z; G; e3 l6 ugreen = (color >> 8) & BYTE_MASK;
. o7 q! e& _' U- W0 k9 T+ }4 qblue = (color >> 16) & BYTE_MASK;

* r- A+ L$ Z% d$ [1 V
3. 位字段 ( bit field )

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

struct ap_queue_status {
( l- \& L- ^! @* h3 B6 E unsigned int queue_empty : 1;! |+ `. J! g: |. u" U
    ...7 C) e6 F; s2 L- R
unsigned int response_code : 8;
. P2 h; L6 b7 G9 L unsigned int pad2  : 16;
& h. w$ M) ?3 d6 x5 g} aqs;

/ I0 I0 }8 Y4 w4 H

给字段赋值:

aqs.queue_empty = 0;
' o3 d' s% @. V$ Raqs.response_code = 0xff;

9 G* Q) L( S# X. y9 p0 Q' \4 G

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

位字段占用的空间:

struct {) }4 R- @& _0 v: A
    unsigned int autfd : 1;
& k& [6 G; J( t9 \: j    unsigned int bldfc : 1;+ b, M5 w) o& Z, J% b8 ]
    unsigned int undln : 1;
- S  P9 V+ W( i* O    unsigned int itals : 1;1 {+ V- J4 r) B0 u. ?, k' O7 \7 G" P
} prnt;
2 T4 [: s: j; `$ _& n+ T* S* b; R5 G; v/ ^9 r& D2 O1 M
struct {. q; z8 |$ R5 W: H# u
    unsigned int code1 : 2;
, T; s* U8 K" X8 ~& k$ n, m5 }& a! J+ ~    unsigned int code2 : 2;
7 ?7 i2 }6 r. l9 ~& K& s    unsigned int code3 : 6;0 K% B6 V5 `  @9 P
    unsigned int code4 : 8;9 L: Q0 }* Q' s5 S" _' O, V  f+ c- D" U
#if TEST
6 k& e5 ?) h; L2 R, b    unsigned int code5 : 10;
2 X0 `! f- |5 y7 H0 K    unsigned int code6 : 12;
4 H! T' [# C7 R& f7 R7 t& ^    unsigned int code7 : 24;6 y/ d8 Y6 G# k1 |1 M. M
#endif
" r5 c  K0 c+ f1 E9 n5 V( V2 a0 v} prcode;( m* i. T  u3 _/ B: l8 L. ^  K' v+ D

, s$ _) N8 t8 x0 eint main(void)
0 k: j& E0 Y' U1 s+ u1 \{
/ ^* D' o8 x2 ]8 F2 t5 r3 M    printf("%ld %ld\n", sizeof(prnt), sizeof(prcode));. I, X& Q/ x- j6 `8 R8 I
}
$ M7 h& K- N/ u4 q

测试结果:

4 4     // without TEST  O1 \$ M8 V& J
4 12    // with TEST

. v3 K5 I8 ~7 ~6 k: P

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

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


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

演示 demo:

int main(void)
1 ?4 G. n, c( d; ~{
0 C! f; ~( \, L/ f: G    int x = 1;
' W0 Q# Z3 Y8 |4 x2 n    3 L2 D( T2 r  j" _" Z' W/ L0 l
    if (*((char *)&x) == 1)
% V! v5 f1 K8 z        printf("little - endian\n");  i. Q0 M2 Z+ W6 l
    else# v/ T8 b/ D$ b, t' e8 l% l
        printf("big - endian\n");
/ t6 x5 m8 V. R! B, x3 ]' c9 e5 G$ ]. h  T
    return 0;! y! L- S3 X* P
}

5 |% I' [" Y7 k6 p/ G

运行效果:

$ gcc byte_order.c -o byte_order
  F3 A8 `" D1 y* k2 g  \% z$ ./byte_order
* J: A( q, g" v8 h; plittle - endian

% B; |# _" {: r( U5 L

代码解析:

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

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

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

    # ~. F/ J$ r% o6 D) @$ j

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

演示 demo:

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

#define BUF_SIZE (33)( u, a1 E4 T$ b
char *baseconv(unsigned int num,int base)
( c: A# D: g7 w. l{
6 s, L( ~  G6 C8 g  y    static char retbuf[BUF_SIZE];
' ^, E8 U3 z$ _# y( w% Q0 M    char *p;
: l1 E# X( V# P( m  B; U6 j4 [/ D    ( v3 j) _- G9 |1 W; t
    ...4 T4 A7 ?- D' N7 t" }7 H
  x- @3 H5 o& ]. {7 C
    p = &retbuf[sizeof(retbuf)-1];4 L4 f; U9 Q8 A; w
    *p='\0';
3 v% a  t! z' Y# Q
9 U2 L+ h9 @: p0 r& @    do {
& R$ W5 n# r5 r6 K1 f4 z        *--p="0123456789abcdef"[num % base];
+ K$ N8 e! S. r' u3 j        num /=base;4 l, D6 o7 I: n& S( P2 L; C- l/ @4 r# W
    } while(num !=0);, {/ Q1 \( n1 C$ w5 _3 R" V1 G
: w! |: o3 \  o5 {
    return p;
2 z- d6 B! c# _  w! V}
; ~' P) D4 h1 S- A2 o

在 main() 中进行测试:

int main(void)5 C" x; i% v3 Y+ {" z' \
{2 y" A) U% v  I) {, ], L
    int a = 20;
; d/ @* ~" i7 {5 O
* S. w# M3 y, d) E  P    printf("%s\n", baseconv(a, 2));
2 L* X+ W! ]# ?    printf("%s\n", baseconv(a, 16));$ }8 N2 O8 }6 B, Z
    2 h' y, o7 e5 @5 v
    return 0;
" X: m, L2 ~  G4 S3 `}
  T- k7 P; m  x  w, V) C

运行效果:

$ gcc int_conv.c -o int_conv% G8 _1 f' R: c6 i" {+ T% L
$ ./int_conv 3 ~, K7 F6 g( X
10100
( \& {7 A' @" k, U, ^( v  Q14

- J9 H& m" ?$ U4 z4 D

代码解析:

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

  • 在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) 冲刷掉了。


    & B/ P5 L- W/ R3 u, I/ Q9 J! |) ~

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

演示 demo:

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

static int bitcounts[] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};. y8 p0 Z- ^2 u" i+ H9 a
# |) }5 ^$ w6 ^* E& `
int bitcount(unsigned int u)% f% e4 |4 i2 j$ o
{
) h8 P$ U! j* p5 I    int n=0;( t5 H1 D- l/ k% M3 ~- ]# A' y
. g! i, M+ O1 m( }: ]1 x9 c
    for(; u!=0; u>>=4)
7 d1 S: C3 q  q  ]- k) o" `        n += bitcounts[u & 0x0f];
$ g+ \0 D( z% r9 G
" e3 D/ J+ t+ K5 f' ^6 |. D    return n;% w! p2 `/ X3 U4 A6 j: e5 B
}
7 J$ ^2 R* T. ^" h- D5 w8 d

在 main() 中进行测试:

int main(void)
" C, n% i0 Q( \{
: Y. ]) Y% [1 |& G    int i = 0;% }8 u- b: u5 j( D
& ?0 _. U7 J8 d- g8 s( n: |; m5 a  b
    for (i=0; i<=0x0f; i++): E: n) |8 u6 w" |
        printf("%d\n", bitcount(i));
9 x: Z# u; R1 D* S' e' _
7 e; A! e$ y# R; y5 V    return 0;
& T# C/ _$ j; Y- K}

0 r$ p0 `4 @8 @

运行效果:

$ gcc bit_counts.c -o bit_counts
3 C) _2 _- C/ K5 i* o/ {$ B  t$ ./bit_counts
9 M1 r% t% P2 \9 v5 I+ {: D7 q0 Z0
7 `3 Z) q( l% I- A1
  O- w  N/ d9 o6 O1
8 C1 x% f! A0 F, Y2
$ E: E4 O7 u* o# W  p* o18 }2 K' `' T* h9 R# x5 I) @
2
3 i2 |+ Y7 ?1 m! y4 T" ]0 E6 i2 o2
% w9 Y7 p( T: o' Q7 w% F& H. h31 |$ t% I4 A& _6 X1 x; A1 Z' s
1
: g8 _. T; d5 X6 b; k23 q1 W* Q2 |/ s* a8 r) U7 j
2* _9 V$ f; g2 Q" w
3
# b( z* u* d4 K" E. H$ i2; |, g( E2 X2 m. C
3
6 }' P" {" q7 B1 `8 O3( t2 P, r& q2 S
4

3 c( x4 l4 @) ~

代码解析:

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

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


    ( {, Y! B* b+ k5 e2 G. c

6 ^. s4 j5 [: J9 \
赞 收藏 1 评论0 发布时间:2020-7-31 21:27

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版