C/C++位域之我见

来源:互联网 发布:阿里云logo含义 编辑:程序博客网 时间:2024/04/29 03:03
很早想说说这个问题了,经常也会有很多公司拿位域出来考人,呵呵要真的想弄清楚还要一点点的分析。

这里先看看网宿的一道笔试题目:

//假设硬件平台是intel x86(little endian)

char *inet_ntoa(uint32_t in)
{
static char b[18];
register char *p;
p = (char *)in;
#define UC(b) (((int)b)&0xff)
(void) snprintf(b, sizeof(b),
"%d. %d. %d", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
return (b);
}

int main()
{
printf("%s, %s", inet_ntoa(0x12345678), inet_ntoa(87654321));
}
有点难度的一道题目,其实理解的也很简单。

位域分析

位域是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,哈哈。

 

原创粉丝点击