C/C++ 位域

 1 //假设硬件平台是intel x86(little endian)
 2
 3 typedef unsigned int uint32_t;
 4 void inet_ntoa(uint32_t in)
 5 {
 6     char b[18];
 7     register char *p;
 8     p = (char *)∈
 9 #define UC(b) (((int)b)&0xff)
10     sprintf(b, "%d.%d.%d.%d/n", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
11     printf(b);
12 }
13 int main()
14 {
15    inet_ntoa(0x12345678);
16    inet_ntoa(0x87654321);
17 } 

有点难度的一道题目,其实理解的也很简单。

位域(Bit-fields)分析

位域是c++和c里面都有的一个概念,但是位域有一点要注意的有很多问题我们一样样的看:

大端和小端字节序

这个很简单,就是起始点该怎么确定。
先看一个程序:

 1     union {
 2         struct
 3         {
 4             unsigned char a1:2;
 5             unsigned char a2:3;
 6             unsigned char a3:3;
 7         }x;
 8         unsigned char b;
 9     }d;
10     int main(int argc, char* argv[])
11     {
12         d.b = 100;
13         return 0;
14     }

那么x的a1,a2,a3该怎么分配值,100的二进制是:0110 0100,那么a1到a3是不是就是依次取值恩?
不是!
我们先看看100分配位的低端是左边的0还是右边的0?很明显是右边的0,那么我们再看a1到a3的分配是从低端到高端的
那么,对应的应该是
<<<<<<--内存增大
a3   a2  a1
011  001 00

内存增大之所以这么写是因为,011是在高位!
而不是通常认为的的:
a1   a2  a3
011  001 00

还有一个情况多见就是一个二进制的数字转化为点分十进制数值,如何进行,这里涉及到大端还是小端的问题,上面没有涉及,主要是因为上面是一个字节,没有这个问题,多个字节就有大端和小端的问题了,或许我们应该记住这一点就是,在我们的计算机上面,大端和小端都是以字节为准的,当然严格来说更应该以位为准不是吗?具体可以参考维基百科上面的一篇文章,他给出了一个以位为准的大小端序的图:

http://en.wikipedia.org/wiki/Endianess

下面研究字节为单位的大小端序,继续看代码吧,如下:

1     int main(int argc, char* argv[])
2     {
3         int a = 0x12345678;
4         char *p = (char *)&a;
5         char str[20];
6         sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
7         printf(str);
8         return 0;
9     }

这个程序假设是小端字节序,那么结果是什么?
我们看看应该怎么放置呢?
每个字节8位,0x12345678分成4个字节,就是从高位字节到低位字节:12,34,56,78,那么这里该怎么放?如下:
---->>>>>>内存增大
78 56 34 12

因为这个是小端,那么小内存对应低位字节,就是上面的结构。

接下来的问题又有点迷糊了,就是p怎么指向,是不是指向0x12345678的开头--12处?不是!12是我们所谓的开头,但是不是内存

的开始处,我们看看内存的分布,我们如果了解p[0]到p[1]的操作是&p[0]+1,就知道了,p[1]地址比p[0]地址大,也就是说p的地址

也是随内存递增的!

12 ^ p[3]
    |
34 | p[2]
    |
56 | p[1]
    |
78 | p[0]
内存随着箭头增大!同时小端存储也是低位到高位在内存中的增加!
这样我们知道了内存怎么分布了

那么:

    sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);

str就是这个结果了:
120.86.52.18

那么反过来呢?

1     int main(int argc, char* argv[])
2     {
3         int a = 0x87654321;
4         char *p = (char *)&a;
5         char str[20];
6         sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
7         printf(str);
8         return 0;
9     }

依旧是小端,8位是一个字节那么就是这样的啦:

87 ^ p[3]
     |
65 | p[2]
    |
43 | p[1]
    |
21 | p[0]

结果是:
33.67.101.-121
为什么是负的?因为系统默认的char是有符号的,本来是0x87也就是135,大于127因此就减去256得到-121
那么要正的该怎么的弄?
如下就是了:

1     int main(int argc, char* argv[])
2     {
3         int a = 0x87654321;
4         unsigned char *p = (unsigned char *)&a;
5         char str[20];
6         sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
7         printf(str);
8         return 0;
9     }

用无符号的!
结果:
33.67.101.135

位域的符号(正负)

看完大端和小端以后,再看看位域的取值的问题,上面我们谈到了一些,首先就是位域是按照位来取值的跟我们的int是32位char是8

位一样,很简单,但是,要注意一点就是位域也有正负,指有符号属性的,就是最高位表示的,也会涉及到补码这个一般被认为非常

恶心的东西,看看程序吧:

 1     #include <stdio.h>
 2     #include <stdlib.h>
 3     #include <string.h>
 4     int main(int argc, char** argv)
 5     {
 6         union
 7         {
 8             struct
 9             {
10                 unsigned char a:1;
11                 unsigned char b:2;
12                 unsigned char c:3;
13             }d;
14             unsigned char e;
15         } f;
16         f.e = 1;
17         printf("%d/n",f.d.a);
18         return 0;
19     }

<小端>
那么输出是什么?
换一下:

 1     #include <stdio.h>
 2     #include <stdlib.h>
 3     #include <string.h>
 4     int main(int argc, char** argv)
 5     {
 6         union
 7         {
 8             struct
 9             {
10                 char a:1;
11                 char b:2;
12                 char c:3;
13             }d;
14             char e;
15         } f;
16         f.e = 1;
17         printf("%d/n",f.d.a);
18         return 0;
19     }

输出又是什么?

小端的话,那么,再d.a上面分得1,而这个是无符号的char,那么前者输出是1,没有问题,第二个输出是-1,哈哈。
为什么?
第二个是无符号的,就一个位分得1,那么就是最高位分得1,就是负数,负数用的补码,实际的值是取反加1,就是0+1=1,再取符

号负数,就是-1.

整型提升

最后的打印是用的%d,那么就是对应的int的打印,这里的位域肯定要提升,这里有一点,不管是提升到有符号还是无符号,都是自

己的符号位来补充,而不改变值的大小(这里说的不改变值大小是用相同的符号属性来读取),负数前面都补充1,正数都是用0来补充

,而且也只有这样才能保证值不变,比如,char提升到int就是前面补充24个char的最高位,比如:

     char c = 0xf0;
     int p = c;
     printf("%d %d/n",c,p);

输出:-16 -16
p实际上就是0xfffffff0,是负数因此就是取反加1得到
c是一个负数那么转化到x的时候就是最高位都用1来代替,得到的数不会改变值大小的。
再看:

     char c = 0xf0;
     unsigned int x = c;
     printf("%u/n",x);

得到的结果是4294967280,也就是0xfffffff0,记住,无符号用%u来打印。

地址不可取

最后说的一点就是位域是一个字节单元里面的一段,是没有地址的!

 1 struct BitField
 2   {
 3     unsigned char a:2;  //最低位;
 4     unsigned char b:3;
 5     unsigned char c:3;  //最高位;
 6   };
 7   union Union
 8   {
 9     struct BitField bf;
10     unsigned int n;
11   };
12   union Union ubf;
13   ubf.n = 0;    //初始化;
14   ubf.bf.a = 0; //二进制为: 000
15   ubf.bf.b = 0; //二进制为: 000
16   ubf.bf.c = 1; //二进制为: 001
17   printf("ubf.bf.n = %u\n", ubf.n);
 1 #include <iostream>
 2  #include <memory.h>
 3  using namespace std;
 4  struct A
 5  {
 6      int a:5;
 7      int b:3;
 8  };
 9  int main(void)
10  {
11      char str[100] = "0134324324afsadfsdlfjlsdjfl";
12          struct A d;
13      memcpy(&d, str, sizeof(A));
14      cout << d.a << endl;
15      cout << d.b << endl;
16      return 0;
17  }

在32位x86机器上输出:

高位 00110100 00110011   00110001    00110000 低位
       ‘4‘       ‘3‘       ‘1‘          ‘0‘
其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001

解析:在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素长 度都小于处理器的位数的时候,便以结构体里面最长的元素为对其单位,即结构体的长度一定是最长的数据元素的整数倍;如果有结构体内存长度大于处理器位数的 元素,那么就以处理器的位数为对齐单元。由于是32位处理器,而且结构体中a和b元素类型均为int(也是4个字节),所以结构体的A占用内存为4个字 节。

上例程序中定义了位域结构A,两个个位域为a(占用5位),b(占用3位),所以a和b总共占用了结构A一个字节(低位的一个字节)。

当程序运行到14行时,d内存分配情况:

 高位 00110100 00110011   00110001    00110000 低位       ‘4‘       ‘3‘       ‘1‘          ‘0‘   其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001

d.a内存中二进制表示为10000,由于d.a为有符号的整型变量,输出时要对符号位进行扩展,所以结果为-16(二进制为11111111111111111111111111110000)

d.b内存中二进制表示为001,由于d.b为有符号的整型变量,输出时要对符号位进行扩展,所以结果为1(二进制为00000000000000000000000000000001)

 1  2
 3 #include "stdio.h"
 4
 5 void main(int argn ,char *argv)
 6 {
 7     struct     test {
 8         unsigned a:10;
 9         unsigned b:10;
10         unsigned c:6;
11         unsigned :2;//this two bytes can‘t use
12         unsigned d:4;
13         }data,*pData;
14     data.a=0x177;
15     data.b=0x111;
16     data.c=0x7;
17     data.d=0x8;
18
19     pData=&data;
20     printf("data.a=%x data.b= %x data.c=%x data.d=%xn",pData->a,pData->b,pData->c,pData->d);//位域可以使用指针
21
22     printf("sizeof(data)=%dn",sizeof(data));   //4 bytes ,最常用的情况
23
24     struct testLen{
25     char a:5;
26     char b:5;
27     char c:5;
28     char d:5;
29     char e:5;
30     }len;
31
32     printf("sizeof(len)=%dn",sizeof(len));     //5bytes 规则2
33
34     struct testLen1{
35         char a:5;
36         char b:2;
37         char d:3;
38         char c:2;
39         char e:7;
40         }len1;
41     printf("sizeof(len1) =%dn",sizeof(len1));    //3bytes 规则1
42
43     struct testLen2{
44         char a:2;
45         char :3;
46         char b:7;
47         long d:20; //4bytes
48         char e:4;
49         }len2;
50     printf("sizeof(len2)=%dn",sizeof(len2));  //12 规则3,4,5,总长为4的整数倍,2+3 占1byte,b占1bye 由于与long对其,2+3+7 占4字节,后面 d 与 e进行了优化 占一个4字节
51
52
53     struct testLen3{
54         char a:2;
55         char :3;
56         char b:7;
57         long d:30;
58         char e:4;
59         }len3;
60     printf("sizeof(len3)=%dn",sizeof(len3));//12 规则3,4,5,总长为4的整数倍,2+3 占1byte,b占1bye 由于与long对其,2+3+7 占4字节,后面 d占一个4字节,为了保证与long对其e独占一个4字节
61 }
时间: 2024-10-07 12:50:14

C/C++ 位域的相关文章

字节序转换与结构体位域(bit field)值的读取 Part 2 - 深入理解字节序和结构体位域存储方式

上一篇文章讲解了带位域的结构体,在从大端机(Big Endian)传输到小端机(Little Endian)后如何解析位域值.下面继续深入详解字节序,以及位域存储的方式. (1) 我们知道,存储数字时,对小端机而言,数字的低位,存在低地址,高位存在高地址.大端机正相反. (2) 读取的方式,也是一样的.对于小端机,读出的低地址位作为数字的低位. (3) 此外Big-Endian/Little-Endian存储顺序,不仅仅针对字节,还针对字节内的比特位.对于小端机而言,字节内的8个比特,低地址端比

C言语位域

有些数据在存储时并不需求占用一个完好的字节,只需求占用一个或几个二进制位即可.例如开关只要通电和断电两种形态,用 0 和 1 表现足以,也就是用一个二进位.恰是基于这种思索,C言语又供给了一种叫做位域的数据构造.在构造体界说时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域.请看下面的例子: struct bs{ unsigned m; unsigned n: 4; unsigned char ch: 6; } :前面的数字用来限制成员变量占用的位数.成员 m 没无限制,依据数

C语言中关于位域的介绍

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可. 为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为"位域"或"位段".所谓"位域"是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数.每个域有一个域名,允许在程序中按域名进行操作. 这样就可以把几个不同的对象用一个字节的二进制位域 来表示.一.位域的定义和位域变量的说明位域定

C结构体之位域(位段)

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”.所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数.每个域有一个域名,允许在程序中按域名进行操作. 这样就可以把几个不同的对象用一个字节的二进制位域来表示.一.位域的定义和位域变量的说明 位域定义与结构定义相仿,其形式为: struct 位域结构

C/C++ 位域知识小结

C/C++ 位域知识小结 几篇较全面的位域相关的文章: http://www.uplook.cn/blog/9/93362/ C/C++位域(Bit-fields)之我见 C中的位域与大小端问题 内存对齐全攻略–涉及位域的内存对齐原则 本文主要对位域相关知识进行了一下梳理,参考如下: C语言中的位域 史上最全的C位域总结2 C结构体之位域(位段) C/C++中以一定区域内的位(bit)为单位来表示的数据成为位域,位域必须指明具体的数目. 位域的作用主要是节省内存资源,使数据结构更紧凑. 1. 一

C语言位域

原贴地址http://www.cnblogs.com/bigrabbit/archive/2012/09/20/2695543.html 有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”.所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数.每个域有一个域名,允许在程序中按域名进行操作. 这样就可

C语言位域和大小端

工作中经常需要解析收到的数据报文,而报文中很多协议字段都用bit来表示.一般都会使用指针偏移然后右移的方式来获取响应的bit位的值.比如下面这样一个报文: D的值为:((pucPktAddr + 3)>>6) & 0x3  --偏移3个字节,右移6位,再与上掩码 E的值就是((pucPktAddr + 3)>>3) & 0x7 感觉不是很直观,可以试着用位域的方法来获取 1 typedef struct XXX { 2 unsigned char D:2 3 uns

位域及大小端一题

In little-endian systems, what is the result of following C program ? #include <stdio.h> typedef struct bitstruct { int b1:5; int :2; int b2:2; } bitstruct; int main() { bitstruct b; memcpy(&b, "EMC EXAMINATION", sizeof(b)); printf(&qu

位域使用记录

1.位域变量符号位也占1bit,所以取1bit时需留意是否是无符号类型 2.位域变量不能取地址 3.初始化时,位域变量按定义顺序初始化,否则会有告警 4.static成员变量不在sizeof计算内 5.空类的sizeof值为1 6.有virtual函数的class,包含指向虚函数表的指针,在64位系统上时占8Byte 7.类继承时对齐,需考虑父类和成员类对象.可参考: http://www.cnblogs.com/caixu/archive/2011/10/11/2207423.html [C+