`
anna_zr
  • 浏览: 200508 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

C位域

阅读更多

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ztz0223/archive/2008/12/24/3599016.aspx


很早想说说这个问题了,经常也会有很多公司拿位域出来考人,呵呵要真的想弄清楚还要一点点的分析。

这里先看看网宿的一道笔试题目,这道题目我之前是复制网上的,结果不对,修改了一下,可以正确运行了,谢谢(imafish_i )提醒:

//假设硬件平台是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

还有一个情况多见就是一个二进制的数字转化为点分十进制数值,如何进行,这里涉及到大端还是小端的问题,上面没有涉及,主要

是因为上面是一个字节,没有这个问题,多个字节就有大端和小端的问题了,如下:

int main(int argc, char* argv[])
{
    int a = 0x12345678;
    char *p = (char *)&a;
    char str[20];
    sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    printf(str);
    return 0;
}
这个程序假设是小端字节序,那么结果是什么?
我们看看应该怎么放置呢?
每个字节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

那么反过来呢?

int main(int argc, char* argv[])
{
    int a = 0x87654321;
    char *p = (char *)&a;
    char str[20];
    sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    printf(str);
    return 0;
}
依旧是小端,8位是一个字节那么就是这样的啦:

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

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

int main(int argc, char* argv[])
{
    int a = 0x87654321;
    unsigned char *p = (unsigned char *)&a;
    char str[20];
    sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    printf(str);
    return 0;
}
用无符号的!
结果:
33.67.101.135



位域的符号(正负)



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

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

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


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv)
{
    union
    {
        struct
        {
            unsigned char a:1;
            unsigned char b:2;
            unsigned char c:3;
        }d;
        unsigned char e;
    } f;
    f.e = 1;
    printf("%d\n",f.d.a);
    return 0;
}

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv)
{
    union
    {
        struct
        {
            char a:1;
            char b:2;
            char c:3;
        }d;
        char e;
    } f;
    f.e = 1;
    printf("%d\n",f.d.a);
    return 0;
}

输出又是什么?

小端的话,那么,再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来打印。



地址不可取


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



附录



最后附上《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:

struct {
      /* field 4 bits wide */
      unsigned field1 :4;
      /*
       * unnamed 3 bit field
       * unnamed fields allow for padding
       */
      unsigned        :3;
      /*
       * one-bit field
       * can only be 0 or -1 in two's complement!
       */
      signed field2   :1;
      /* align next field on a storage unit */
      unsigned        :0;
      unsigned field3 :6;
}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语言中的位域的使用

    ### C语言中的位域使用详解 #### 一、位域的概念及作用 在C语言编程中,有时我们可能需要处理一些只需要几个比特位就能表示的数据,例如开关状态、某些标志位等。在这种情况下,如果使用普通的整型或者字符型变量...

    C语言 位域PPT课件.pptx

    位域是C语言中结构体(struct)的一种特殊形式,它允许我们定义结构体成员的位长度,使得我们可以精确控制存储空间的使用。位域主要用于处理那些只需要少量存储空间(小于一个字节)的数据,比如标志位(flags)等。...

    详解C语言位域的使用与注意事项

    所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。...下面这篇文章就给大家介绍下关于C语言中位域的使用与注意事项。

    C语言中关于位域的概念和使用

    描述了在C语言中如何对数据的位进行操作,使用FPGA的嵌入式设计应用

    C语言 位域PPT学习教案.pptx

    位域是C语言中结构体(struct)的一种特殊形式,它允许我们定义结构体成员的位宽,即占用存储器中的位数。位域的主要目的是有效地利用存储空间,特别是处理那些需要精确控制位数的硬件接口或者数据结构。在位域定义...

    浅析C语言位域和位段

    以下是对C语言中的位域和位段进行了详细的分析介绍,需要的朋友可以过来参考下

    C语言 位域详解及示例代码

    位域是C语言中一种特殊的数据结构,它允许我们在结构体中定义成员变量并指定它们占用的二进制位数,以更高效地利用内存空间。在某些场景下,如处理硬件寄存器或存储位级别的数据,位域非常有用。本文将详细介绍C语言...

    Linux c位域操作完整版 经典版

    Linux c位域操作完整版 经典版 Linux c位域操作完整版 经典版

    C语言结构体之位域详解

    文章主要对C语言结构体的位域知识进行详细介绍。

    linux c 简单位域操作

    linux c 位域操作 报文 位域操作 报文 位域操作 报文 简单位域操作 位域操作 报文 位域操作 报文 位域操作 报文

    c/c++类的位域详细介绍

    "C/C++位域详细介绍" C/C++中的位域(Bit Field)是指在结构体中使用的位域成员,它们可以将整数成员存储到比编译器通常允许的更小的内存空间中。位域的宽度可以通过常量整数表达式来指明。这种技术广泛应用于需要...

    数据结构位域

    位域是C语言提供的一种特殊的数据结构,主要用于节省存储空间。在很多情况下,某些变量的值并不需要占用一个完整的字节,仅需占用几位二进制位就足够了。比如表示一个开关的状态,只需要一位即可。这种情况下使用...

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

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

    位域探秘:C语言中的位域全解析

    C语言是一种广泛使用的计算机编程语言,具有以下特点: 1. **高级语言**:C语言是一种高级编程语言,设计用于编写系统软件和应用程序。 2. **结构化语言**:C语言支持结构化编程,允许使用条件语句、循环、函数等...

    关于大小端、位域的一些概念详解

    大小端: 对于像C++中的char这样的数据类型,它本身就是占用一个字节的大小,不会产生什么问题。但是当数制类型为int,在32bit的系统中,它需要占用4个字节(32bit),这个时候就会产生这4个字节在寄存器中的存放...

    C语言中的位域高级应用:技巧与实例分析

    位域是C语言中一种强大的特性,它允许程序员精确控制数据的存储和访问。通过合理使用位域,可以有效地节省内存空间,提高数据处理效率。然而,由于位域的存储和对齐方式可能因编译器和平台而异,因此在跨平台编程时...

    IAR FOR STM8位域的定义和应用

    位域(Bit Field)是C语言提供的一种特性,允许我们为一个结构体内的变量定义更小的单位,例如单个比特。在STM8的硬件特性中,有些寄存器包含多个独立控制或状态位,位域就非常适合用来访问和操作这些位。 1. **...

Global site tag (gtag.js) - Google Analytics