`

C/C++位域(Bit-fields)之我见

阅读更多

原文 : http://blog.csdn.net/ztz0223/archive/2008/12/24/3599016.aspx


# //假设硬件平台是intel x86(little endian)    
#   
# typedef unsigned int uint32_t;   
# void inet_ntoa(uint32_t in)    
# {    
#     char b[18];    
#     register char *p;    
#     p = (char *)∈    
# #define UC(b) (((int)b)&0xff)    
#     sprintf(b, "%d.%d.%d.%d\n", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));    
#     printf(b);    
# }    
# int main()    
# {    
#    inet_ntoa(0x12345678);  
#    inet_ntoa(0x87654321);    
# }   
 

 

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


位域(Bit-fields)分析

 

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


大端和小端字节序


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

    union {
    	struct   
    	{
    		unsigned char  a1:2;
    		unsigned char  a2:3;
    		unsigned char  a3:3;
    	}x;
    	unsigned char  b;
    }d;
    
    int  main(int  argc, char * argv[])
    {
    	d.b = 100;
    	return  0;
    }
     

      那么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]
      内存随着箭头增大!同时小端存储也是低位到高位在内存中的增加!
      这样我们知道了内存怎么分布了

      那么:

      1. 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的最高位,比如:

      1.  char  c = 0xf0; 
      2.  int  p = c;
      3.  printf("%d %d\n" ,c,p);

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

      1.  char  c = 0xf0;
      2.  unsigned int  x = c;
      3.  printf("%u\n" ,x);

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

      地址不可取


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


      附录

       

      最后附上《The C Book》这本书的一段说法:
      While we're on the subject of structures, we might as well look at bitfields. They can only be declared inside a

      structure or a union, and allow you to specify some very small objects of a given number of bits in length. Their

      usefulness is limited and they aren't seen in many programs, but we'll deal with them anyway. This example should

      help to make things clear:

      1. struct  {
      2.       /* field 4 bits wide */
      3.       unsigned field1 :4;
      4.       /*
      5.        * unnamed 3 bit field
      6.        * unnamed fields allow for padding
      7.        */
      8.       unsigned        :3;
      9.       /*
      10.        * one-bit field
      11.        * can only be 0 or -1 in two's complement!
      12.        */
      13.       signed field2   :1;
      14.       /* align next field on a storage unit */
      15.       unsigned        :0;
      16.       unsigned field3 :6;
      17. }full_of_fields;

      Each field is accessed and manipulated as if it were an ordinary member of a structure. The keywords signed and

      unsigned mean what you would expect, except that it is interesting to note that a 1-bit signed field on a two's

      complement machine can only take the values 0 or -1. The declarations are permitted to include the const and

      volatile qualifiers.

      The main use of bitfields is either to allow tight packing of data or to be able to specify the fields within some

      externally produced data files. C gives no guarantee of the ordering of fields within machine words, so if you do

      use them for the latter reason, you program will not only be non-portable, it will be compiler-dependent too. The

      Standard says that fields are packed into ‘storage units’, which are typically machine words. The packing order, and

      whether or not a bitfield may cross a storage unit boundary, are implementation defined. To force alignment to a

      storage unit boundary, a zero width field is used before the one that you want to have aligned.

      Be careful using them. It can require a surprising amount of run-time code to manipulate these things and you can

      end up using more space than they save.

      Bit fields do not have addresses—you can't have pointers to them or arrays of them.


      最后

      了解了这些我想网宿的那道题目也很简单了。希望大家指正。

      分享到:
      评论

      相关推荐

        c c++位域研究总结!!!

        位域(Bit-fields)是 C 语言和 C++ 语言中都有的一个概念,但是位域有很多需要注意的问题。其中一个重要的问题是大端和小端字节序的问题。 大端和小端字节序是计算机科学中的一种约定,用于描述多字节数字在计算机...

        C/C++笔试题

        位域(bit fields)允许在结构体中定义具有固定位数的成员。这在某些应用场景下可以节省内存空间,如硬件接口编程。位域的使用需要特别小心,因为它们的行为可能依赖于特定的编译器和处理器架构。 #### 27. 字符串...

        C++ 旅程

        #### 一、C/C++中的位域(Bit Fields) **概念解释:** 位域(Bit Fields),也被称为位段,是C/C++中的一种特殊的数据结构,它允许在一个整数类型(通常是`unsigned int`)中定义一系列具有固定宽度的字段,每个...

        linux开发常用参考手册合集

        而“关于C、C++位域(Bit-fields)的看法.doc”则讨论了位字段这一C/C++中的特殊数据结构,解释了如何利用位字段节省存储空间和优化数据结构。 最后,“apue2e_src.rar”可能包含《Advanced Programming in the UNIX ...

        嵌入式CC++语言精华文章集锦

        - **位域(Bit Fields)**: 可以在一个整数类型中定义多个位作为单独的变量使用,节省内存空间。 **1.4 示例** 以下是一个示例,展示了如何使用结构体和联合体来实现一种通用的通信协议: ```c typedef struct { ...

        嵌入式C C++语言精华文章

        - **位域操作**:通过使用位域(`bit fields`),可以在单个`struct`成员中表示多个布尔值或枚举值,从而节省空间资源。 #### C++中extern "C"含义深层探索 **知识点概述**:`extern "C"`是C++中用于处理C和C++混合...

        c++编程常遇问题总结

        2. 位域型(Bit Fields): 位域是C++中一种节省存储空间的数据结构,允许我们定义结构体中的成员只占用指定的位数。定义位域的基本语法如下: ```cpp struct BitFieldStruct { 数据类型 成员名 : 长度; }; ``` ...

        华为C++中级培训胶片ppt

        类名称、成员变量、成员函数(包括普通成员函数和静态成员函数)、联合体(Union)、位域(Bit Fields)、嵌套类声明、类型名在类作用域内的定义、多重继承、虚函数、抽象类以及控制类成员访问权限(private、...

        华为内部教材:C++中级培训教程(PPT)

        类可以包含命名空间(namespace)、类名(class names)、成员变量(class members)、成员函数(member functions)、静态成员函数(static member functions)、联合体(union)、位域(C++ Bit Fields)、嵌套类(nested class ...

        C++查看数据二进制形式

        4. **位域(Bit Fields)**:C++支持在结构体中定义位域,这允许你直接操作单个比特。这对于查看和理解数据的二进制结构非常有帮助,尤其是在处理紧凑的数据格式时。 5. **内存映射文件(Memory-Mapped Files)**:...

        C++使用培训教材,工程使用

        - **位字段(C++ Bit Fields)**:允许在结构或类中定义位域,节省内存。 - **嵌套类声明(Nested Class Declarations)**:类内定义的类,可以增加封装性。 - **类型名称在类作用域(Type Names in Class Scope)**...

        OllyICE,od

        OllyDbg 是一种具有可视化界面的 32 位汇编-分析调试器。它的特别之处在于可以在没有源...不仅如此,OllyDbg还能识别大量的常量符号名(如:窗口消息、错误代码、位域[bit fields]…)并能够解码为已知的函数调用。

        bitwise_operation.rar_matlab例程_C++_

        此外,C++还提供了一些特殊的位操作,如位域(bit fields)和位掩码(bit masks),它们在处理结构体和定义状态标志时非常有用。 文件“bitwise_operation”很可能是一个Matlab或C++的源代码文件,展示了如何在实际...

        求字节运算符.zip

        5. **编译器优化与位域**:位字段(bit fields)允许在一个字节或多个字节中定义和操作结构体的各个部分,节省内存空间。编译器可能对位字段进行优化,但其行为可能因编译器和平台而异,需要谨慎使用。 6. **二进制...

        C 语言编程常见问题解答.chm

        10.3 位域(bit fields)是可移植的吗? 10.4 移位和乘以2这两种方式中哪一种更好? 10.5 什么是高位字节(high-order byte)和低位字节(low-order byte)? 10.6 16位和32位的数是怎样存储的? 第11章 调试 ...

      Global site tag (gtag.js) - Google Analytics