非淡泊无以明志,非宁静无以致远
全部博文(408)
分类: 嵌入式
2011-03-01 17:28:04
位域是c++和c里面都有的一个概念,但是位域有要注意的很多问题:
1.大端和小端字节序
这个很简单,就是起始点该怎么确定。
先看一个程序:
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
还有一个情况多见就是一个二进制的数字转化为点分十进制数值,如何进行,这里涉及到大端还是小端的问题,上面没有涉及,主要是因为上面是一个字节,没有这个问题,多个字节就有大端和小端的问题了,或许我们应该记住这一点就是,在我们的计算机上面,大端和小端都是以字节为准的,当然严格来说更应该以位为准不是吗?
具体可以参考维基百科上面的一篇文章,他给出了一个以位为准的大小端序的图:
下面研究字节为单位的大小端序,继续看代码吧,如下:
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
2.位域的符号(正负)
看完大端和小端以后,再看看位域的取值的问题,上面我们谈到了一些,首先就是位域是按照位来取值的跟我们的int是32位char是8位一样,很简单,但是,要注意一点就是位域也有正负,指有符号属性的,就是最高位表示的,也会涉及到补码这个一般被认为非常恶心的东西,看看程序吧:
1.#include
2.#include
3.#include
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
2.#include
3.#include
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.
3.整型提升
最后的打印是用的%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来打印。
地址不可取
最后说的一点就是位域是一个字节单元里面的一段,是没有地址的!