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

本文详细解析了位域(Bit-fields)的概念及其在C/C++中的应用,并深入探讨了大端和小端字节序的区别及其实现细节。

前言

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

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

  1. //假设硬件平台是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里面都有的一个概念,但是位域有一点要注意的有很多问题我们一样样的看:

大端和小端字节序


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

  1. union{
  2. struct
  3. {
  4. unsignedchara1:2;
  5. unsignedchara2:3;
  6. unsignedchara3:3;
  7. }x;
  8. unsignedcharb;
  9. }d;
  10. intmain(intargc,char*argv[])
  11. {
  12. d.b=100;
  13. return0;
  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. intmain(intargc,char*argv[])
  2. {
  3. inta=0x12345678;
  4. char*p=(char*)&a;
  5. charstr[20];
  6. sprintf(str,"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
  7. printf(str);
  8. return0;
  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. intmain(intargc,char*argv[])
  2. {
  3. inta=0x87654321;
  4. char*p=(char*)&a;
  5. charstr[20];
  6. sprintf(str,"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
  7. printf(str);
  8. return0;
  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. intmain(intargc,char*argv[])
  2. {
  3. inta=0x87654321;
  4. unsignedchar*p=(unsignedchar*)&a;
  5. charstr[20];
  6. sprintf(str,"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
  7. printf(str);
  8. return0;
  9. }

用无符号的!
结果:
33.67.101.135

位域的符号(正负)

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

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

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

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. intmain(intargc,char**argv)
  5. {
  6. union
  7. {
  8. struct
  9. {
  10. unsignedchara:1;
  11. unsignedcharb:2;
  12. unsignedcharc:3;
  13. }d;
  14. unsignedchare;
  15. }f;
  16. f.e=1;
  17. printf("%d\n",f.d.a);
  18. return0;
  19. }

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

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. intmain(intargc,char**argv)
  5. {
  6. union
  7. {
  8. struct
  9. {
  10. chara:1;
  11. charb:2;
  12. charc:3;
  13. }d;
  14. chare;
  15. }f;
  16. f.e=1;
  17. printf("%d\n",f.d.a);
  18. return0;
  19. }

输出又是什么?

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

号负数,就是-1.

整型提升

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

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

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

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

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

  1. charc=0xf0;
  2. unsignedintx=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. /*field4bitswide*/
  3. unsignedfield1:4;
  4. /*
  5. *unnamed3bitfield
  6. *unnamedfieldsallowforpadding
  7. */
  8. unsigned:3;
  9. /*
  10. *one-bitfield
  11. *canonlybe0or-1intwo'scomplement!
  12. */
  13. signedfield2:1;
  14. /*alignnextfieldonastorageunit*/
  15. unsigned:0;
  16. unsignedfield3: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.

最后

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

#include <cstdint> #include <string> #include <vector> #include <google/protobuf/message.h> #include <iostream> #include <cstring> // 定义结构体 struct SiteTypeBits { uint32_t numpins : 14; // bit0-13: 总引脚数 uint32_t numinputs : 14; // bit14-27: 输入引脚数 uint32_t optinv : 1; // bit28: optinv标志 uint32_t rbel : 1; // bit29: rbel标志 uint32_t reserved1 : 2; // bit30-31: 保留 }; struct SiteTypeBits1 { uint32_t deviceid : 4; // bit0-3: 设备ID uint32_t numoutputs : 14; // bit4-17: 输出引脚数 uint32_t index : 12; // bit18-29: 索引 uint32_t reserved2 : 2; // bit30-31: 保留 }; struct SiteTypeBits2 { uint32_t numbels : 6; // bit0-5: BEL数量 uint32_t numrbels : 13; // bit6-18: RBEL数量 uint32_t numarcs : 6; // bit19-24: ARC数量 uint32_t reserved3 : 7; // bit25-31: 保留 }; struct SiteTypeBits3 { uint32_t numconns : 14; // bit0-13: 连接数量 uint32_t reserved4 : 18; // bit14-31: 保留 }; // Protobuf消息映射 namespace HDDMXng { class SiteType : public google::protobuf::Message { public: // 字段定义与原始protobuf消息完全一致 optional uint32 numinputs = 1; optional uint32 numoutputs = 2; optional uint32 numpins = 3; optional bool istest = 4; optional uint32 numbels = 5; optional uint32 numrbels = 6; optional uint32 numarcs = 7; optional string name = 8; optional string hwname = 9; optional uint32 numconns = 10; }; } class HDDMSiteType { private: union { uint32_t sitetypecode; SiteTypeBits sitetypecode_bits; }; union { uint32_t sitetypecode1; SiteTypeBits1 sitetypecode1_bits; }; union { uint32_t sitetypecode2; SiteTypeBits2 sitetypecode2_bits; }; union { uint32_t sitetypecode3; SiteTypeBits3 sitetypecode3_bits; }; std::string name; std::string hwname; HDDMSitePinDef* hddmBelPinDefArray; HDDMArc* pipsMatrixVec; HDDMElement** hddmElementArray; HDDMSiteTypeConn** connArray; public: // 构造函数 HDDMSiteType() { memset(this, 0, sizeof(HDDMSiteType)); name = ""; hwname = ""; } // 从Protobuf读取数据 void readme_pb(std::istream* stream, HDDMDevice* hddmDevice) { // 检查并读取标记 if (HDDMDeviceDump::useXngMarks) { char markBuffer[8]; stream->read(markBuffer, 8); } // 初始化类型名称映射 static bool typeMapInitialized = false; if (!typeMapInitialized) { HDDMSiteType::createtypenamemap(); typeMapInitialized = true; } // 读取Protobuf消息 HDDMXng::SiteType sitetype_msg; HDDMDevice::readMessage(stream, &sitetype_msg); // 设置 sitetypecode_bits.numpins = sitetype_msg.numpins() & 0x3FFF; sitetypecode_bits.numinputs = sitetype_msg.numinputs() & 0x3FFF; sitetypecode_bits.optinv = sitetype_msg.istest(); sitetypecode_bits.rbel = sitetype_msg.numrbels() > 0; sitetypecode1_bits.deviceid = sitetype_msg.numoutputs() & 0xF; sitetypecode1_bits.numoutputs = (sitetype_msg.numoutputs() >> 4) & 0x3FFF; sitetypecode1_bits.index = sitetype_msg.numconns() & 0xFFF; sitetypecode2_bits.numbels = sitetype_msg.numbels() & 0x3F; sitetypecode2_bits.numrbels = sitetype_msg.numrbels() & 0x1FFF; sitetypecode2_bits.numarcs = sitetype_msg.numarcs() & 0x3F; sitetypecode3_bits.numconns = sitetype_msg.numconns() & 0x3FFF; // 设置名称 name = sitetype_msg.name(); hwname = sitetype_msg.hwname(); // 读取引脚定义 if (sitetypecode_bits.numpins > 0) { hddmBelPinDefArray = new HDDMSitePinDef[sitetypecode_bits.numpins]; for (uint32_t i = 0; i < sitetypecode_bits.numpins; ++i) { hddmBelPinDefArray[i].readme_pb(stream, hddmDevice); // 设置引脚属性 hddmBelPinDefArray[i].pinDef_code = (i << 8) | (hddmBelPinDefArray[i].pinDef_code & 0xFF00FF); hddmBelPinDefArray[i].pinDef_code2 = (HIWORD(sitetypecode1) & 0xFFC0) | (hddmBelPinDefArray[i].pinDef_code2 & 0x3F); } } // 读取ARC定义 if (sitetypecode2_bits.numarcs > 0) { pipsMatrixVec = new HDDMArc[sitetypecode2_bits.numarcs]; for (uint32_t i = 0; i < sitetypecode2_bits.numarcs; ++i) { pipsMatrixVec[i].readme_pb(stream, hddmDevice); pipsMatrixVec[i].arc_code = i; } } // 读取元素定义 if (sitetypecode2_bits.numbels > 0) { hddmElementArray = new HDDMElement*[sitetypecode2_bits.numbels]; for (uint32_t i = 0; i < sitetypecode2_bits.numbels; ++i) { hddmElementArray[i] = new HDDMElement(); hddmElementArray[i]->readme_pb(stream, hddmDevice); hddmElementArray[i]->element_id = i; hddmElementArray[i]->element_code = (((HIWORD(sitetypecode1) >> 6) & 0x3FF) << 10) | (hddmElementArray[i]->element_code & 0xFFF003FF); } } // 读取连接定义 if (sitetypecode3_bits.numconns > 0) { connArray = new HDDMSiteTypeConn*[sitetypecode3_bits.numconns]; for (uint32_t i = 0; i < sitetypecode3_bits.numconns; ++i) { connArray[i] = new HDDMSiteTypeConn(); connArray[i]->readme_pb(stream, hddmDevice); connArray[i]->conn_code = i; connArray[i]->conn_code2 = (((HIWORD(sitetypecode1) >> 6) & 0x3FF) << 10) | (connArray[i]->conn_code2 & 0xFFF003FF); } } // 设置类型枚举和类别 settypeenum(); setcategory(); } // 写入Protobuf数据 void writeme_pb(std::ostream* stream) { // 写入标记 if (HDDMDeviceDump::useXngMarks) { stream->write("SITETYPE", 8); } // 创建并填充Protobuf消息 HDDMXng::SiteType sitetype_msg; sitetype_msg.set_numpins(sitetypecode_bits.numpins); sitetype_msg.set_numinputs(sitetypecode_bits.numinputs); sitetype_msg.set_istest(sitetypecode_bits.optinv); sitetype_msg.set_numrbels(sitetypecode_bits.rbel ? 1 : 0); sitetype_msg.set_numoutputs((sitetypecode1_bits.deviceid & 0xF) | ((sitetypecode1_bits.numoutputs & 0x3FFF) << 4)); sitetype_msg.set_numconns(sitetypecode1_bits.index & 0xFFF); sitetype_msg.set_numbels(sitetypecode2_bits.numbels); sitetype_msg.set_numrbels(sitetypecode2_bits.numrbels); sitetype_msg.set_numarcs(sitetypecode2_bits.numarcs); sitetype_msg.set_numconns(sitetypecode3_bits.numconns); sitetype_msg.set_name(name); sitetype_msg.set_hwname(hwname); // 写入Protobuf消息 HDDMDevice::writeMessage(stream, &sitetype_msg); // 写入引脚定义 if (HDDMDeviceDump::useXngMarks) { stream->write("PINS", 4); } if (hddmBelPinDefArray && sitetypecode_bits.numpins > 0) { for (uint32_t i = 0; i < sitetypecode_bits.numpins; ++i) { hddmBelPinDefArray[i].writeme_pb(stream); } } // 写入ARC定义 if (HDDMDeviceDump::useXngMarks) { stream->write("ARCS", 4); } if (pipsMatrixVec && sitetypecode2_bits.numarcs > 0) { for (uint32_t i = 0; i < sitetypecode2_bits.numarcs; ++i) { pipsMatrixVec[i].writeme_pb(stream); } } // 写入元素定义 if (HDDMDeviceDump::useXngMarks) { stream->write("ELEMENTS", 8); } if (hddmElementArray && sitetypecode2_bits.numbels > 0) { for (uint32_t i = 0; i < sitetypecode2_bits.numbels; ++i) { hddmElementArray[i]->writeme_pb(stream); } } // 写入连接定义 if (HDDMDeviceDump::useXngMarks) { stream->write("CONNS", 5); } if (connArray && sitetypecode3_bits.numconns > 0) { for (uint32_t i = 0; i < sitetypecode3_bits.numconns; ++i) { connArray[i]->writeme_pb(stream); } } } // 打印函数 void print(std::ostream& stream, const std::string& filename) { // 打印基本信息 stream << filename << " SITETYPE " << name << std::endl; stream << filename << " m_numpins: " << sitetypecode_bits.numpins << std::endl; stream << filename << " m_numinputs: " << sitetypecode_bits.numinputs << std::endl; stream << filename << " m_numoutputs: " << sitetypecode1_bits.numoutputs << std::endl; stream << filename << " m_numbels: " << sitetypecode2_bits.numbels << std::endl; stream << filename << " m_numrbels: " << sitetypecode2_bits.numrbels << std::endl; stream << filename << " m_numarcs: " << sitetypecode2_bits.numarcs << std::endl; stream << filename << " m_numconns: " << sitetypecode3_bits.numconns << std::endl; // 打印引脚定义 if (hddmBelPinDefArray && sitetypecode_bits.numpins > 0) { for (uint32_t i = 0; i < sitetypecode_bits.numpins; ++i) { hddmBelPinDefArray[i].print(stream, filename); if (i != sitetypecode_bits.numpins - 1) { stream << ","; } } stream << ";" << std::endl; } // 打印ARC定义 if (pipsMatrixVec && sitetypecode2_bits.numarcs > 0) { stream << filename << "ARCS:" << std::endl; for (uint32_t i = 0; i < sitetypecode2_bits.numarcs; ++i) { pipsMatrixVec[i].print(stream, filename); if (i != sitetypecode2_bits.numarcs - 1) { stream << ","; } } stream << ";" << std::endl; } // 打印元素定义 if (hddmElementArray && sitetypecode2_bits.numbels > 0) { stream << filename << "ELEMENTS:" << std::endl; for (uint32_t i = 0; i < sitetypecode2_bits.numbels; ++i) { hddmElementArray[i]->print(stream, filename); stream << " " << HDDMDevice::readUint32(hddmElementArray[i]->element_code) << " " << HDDMDevice::readUint32(hddmElementArray[i]->element_code2); if (i != sitetypecode2_bits.numbels - 1) { stream << ","; } } stream << ";" << std::endl; } // 打印连接定义 if (connArray && sitetypecode3_bits.numconns > 0) { stream << filename << "CONNS:" << std::endl; for (uint32_t i = 0; i < sitetypecode3_bits.numconns; ++i) { connArray[i]->print(stream, filename); if (i != sitetypecode3_bits.numconns - 1) { stream << ","; } } stream << ";" << std::endl; } } // 析构函数 ~HDDMSiteType() { // 释放引脚定义数组 if (hddmBelPinDefArray) { delete[] hddmBelPinDefArray; } // 释放ARC数组 if (pipsMatrixVec) { delete[] pipsMatrixVec; } // 释放元素数组 if (hddmElementArray) { for (uint32_t i = 0; i < sitetypecode2_bits.numbels; ++i) { delete hddmElementArray[i]; } delete[] hddmElementArray; } // 释放连接数组 if (connArray) { for (uint32_t i = 0; i < sitetypecode3_bits.numconns; ++i) { delete connArray[i]; } delete[] connArray; } } private: // 设置类型枚举 void settypeenum() { // 根据设置类型枚举 // 实现细节省略... } // 设置类别 void setcategory() { // 根据设置类别 // 实现细节省略... } // 创建类型名称映射 static void createtypenamemap() { // 创建类型名称映射表 // 实现细节省略... } };
最新发布
10-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值