zoukankan      html  css  js  c++  java
  • 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
    ", 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 }
  • 相关阅读:
    git创建一个空的版本库
    程序后台服务启动,MongoDB未启动(启动较慢)/(关机重启情况下)。
    启动客户端后台服务
    客户端后台服务(已注册机器)RabbitMQ未消费的情况
    MongoDB数据重复解决方案
    github中新建一个branch(分支)
    MES-后台服务卸载
    linux 第八章 高级键盘
    socketserver
    jmeter发送邮件的模板
  • 原文地址:https://www.cnblogs.com/balingybj/p/4681513.html
Copyright © 2011-2022 走看看