vlambda博客
学习文章列表

C语言常见陷阱之“纠缠不清的位域”



淘宝店铺:吴鉴鹰的小铺

一.表达式求值--整数类型提升
问题:

  
    
    
  
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. int main()
  5. {
  6. char c;
  7. unsigned char uc;
  8. unsigned char us;
  9. c = 128;// -128-0-127;
  10. uc = 128;
  11. us = c + uc;//256;
  12. printf("0x%x\n", us);
  13. us = (unsigned char)c + uc;
  14. printf("0x%x\n", us);
  15. us = c + (char)uc;
  16. printf("0x%x\n", us);
  17. system("pause");
  18. return 0;
  19. }


输出的结果是()
A :0x0 0x00 0xff00
B :0x100 0x100 0xff00
C:0x00 0x100 0x0
D:0x0 0x100 0x0
答案是A.
首先要搞明白为什么会出现整形类型提升;
C的整形算数表达式运算时总是至少以缺省整形类型的精度来进行计算,为了获得这个精度,将字符型和短整形操作数在使用之前转换为普通整形;这种转换成为是整形提升,然后再执行算数运算,最后将结果截断放到内存中;
接下来我们仔细分析下:如下

  
    
    
  
  1. int main()
  2. {
  3. char c;
  4. unsigned char uc;
  5. unsigned char us;
  6. c = 128;// -128-0-127;
  7. uc = 128;
  8. us = c + uc;//256;
  9. c是有符号型最高位是符号位1111 1111 1111 1111 1111 1111 1000 0000
  10. uc是无符号 0000 0000 0000 0000 0000 0000 1000 0000
  11. 0000 0000 0000 0000 0000 0000 0000 0000
  12. 最后的结果截断ox表示16进制取后16位结果是oxoo
  13. 10000 0000无符号取低八位;为0
  14. printf("0x%x\n", us);
  15. us = (unsigned char)c + uc;
  16. 强制后c 0000 0000 0000 0000 0000 0000 1000 0000
  17. uc 0000 0000 0000 0000 0000 0000 1000 0000
  18. 0000 0000 0000 0000 0000 0001 0000 0000
  19. 结果截断表示ox0f00;
  20. printf("0x%x\n", us);
  21. us = c + (char)uc;
  22. c是有符号型最高位是符号位 1111 1111 1111 1111 1111 1111 1000 0000
  23. uc是有符号型最高位是符号位 1111 1111 1111 1111 1111 1111 1000 0000
  24. 1111 1111 1111 1111 1111 1111 0000 0000
  25. 结果截断表示ox0ff00
  26. printf("0x%x\n", us);
  27. system("pause");
  28. return 0;
  29. }


2.static关键字的使用:
static修饰变量。作用于仅仅局限于被定义的文件中,防止被其他文件调用;
修饰全局变量。作用域在定义处开始,知道文件结尾处结束;
修饰局部变量,在函数体被定义,只能在这个函数里用。

  
    
    
  
  1. int fun(int x, int y)
  2. {
  3. static int m = 0;
  4. static int i = 2;
  5. i += m + 1;//3
  6. //第二次8+3+1
  7. m = i + x + y;//3+x+y
  8. //第二次12+4+1
  9. return m;//8
  10. }
  11. int main()
  12. {
  13. int j = 4;
  14. int m = 1;
  15. int k;//k = 8
  16. k = fun(j, m);// 4 1
  17. printf("%d\n", k);
  18. k = fun(j, m);//4 1
  19. printf("%d\n", k);
  20. system("pause");
  21. return 0;
  22. }


结果是 8和17;
第一次调用fun函数时,x= 4,y = 1;i = 3;m = 3+4+1= 8;
第二次调用fun函数时,x=4;y= 4;i= 3+8+1 =12;m =12+4+1 = 17;
3.“指针+1”的妙用。

  
    
    
  
  1. struct B
  2. {
  3. long A1;
  4. char cA2;
  5. char cA3;
  6. long A4;
  7. long A5;
  8. }*p;
  9. p = (struct B*)0x100000;
  10. p + 0x1 = 100001;
  11. (unsigned long)p + 0x1 = 0x100001;
  12. (unsigned long*)p +0x1 = 0x100004;
  13. (char *)p + 0x1 = 0x100004;


接下来我们分析一下+1实际是加几?

  
    
    
  
  1. struct B
  2. {
  3. long A1; //4
  4. char cA2; //1
  5. char cA3; //1
  6. long A4; //4
  7. long A5; //4
  8. }*p;
  9. //4+1+1+2+4+4 = 16
  10. p = (struct B*)0x100000;
  11. p + 0x1 = 100010;


    p此时表示结构体首地址 + 1 = sizeof(struct B)+0x100000 = 0x100010; 
 
   
   
 
(unsigned long)p + 0x1 = 0x100001;
    表示p强转为unsigned long整型此时表示值+1 = sizeof(unsigned long)+0x10000=0x100001    
 
   
   
 
(unsigned long*)p +0x1 = 0x100004
    表示p强转为unsigned long *结果表示指针+1 =sizeof(unsigned long*) =  0x100004;
 
   
   
 
(char *)p + 0x1 = 0x100004;
    p的类型强转为char*结构表示指针+1 = sizeof(char*)+0x00001 = x100004;
    一般这种的求法都是sizeof(类型)*i
我们详细分析下:还记得前面a+1和&a +1区别吗?指针变量与整数相加减表示指针的地址加上这个整数,而这个整数表示不是字节而是元素的个数。
所以第一个p +0x1表示是结构体的首地址+sizeof(struct B)= 0x100010;第2个表示将p强制转换成unsigned long 整形;此时(unsigned long)p + 0x1表示是值加1 = 0x100001;
第三个将p强制转换(unsigned long*)指针型,所以(unsigned long*)p + 0x1 = 0x100004;最后一个表示(char*)p 指针+0x1 = 0x100004
4.“纠缠不清的”位域。

  
    
    
  
  1. #define MAX_SIZE A+B
  2. struct _Record _Struct
  3. {
  4. unsigned char E : 4;
  5. unsigned char p : 2;
  6. unsigned char stata;
  7. unsigned char a : 1;
  8. }*p;
  9. struct Record _Struct *p = (struct Record _Struct *p)malloc(szieof(struct Record _Struct)*MAX_SZIE);


当A = 2,B = 3时,p分配几个字节()的空间
A 20, B 15, C 11, D 9
答案是D
位域:
有些信息在存储时并不需要占用一个完整的字节;而是占用一个或者几个二进制位,为了节省空间,并且使用方便,C语言提供了一种数据结构,称为位域。“位域”就是指把一个字节化成不同的区域,并且每个区域都有自己的位数;每个域都有自己的域名,允许程序按域名操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:    
struct 位域结构名     
{ 位域列表 };     
其中位域列表的形式为:类型说明符 位域名:位域长度  ;
接下来分析这个题:E表示占用这个字节的前4个位,p占用这个字节后2个位;state表示一个完整的位段,还剩2个位不足以存储,需要重新开辟了新的字节,a占用第三个字节的第一位。
这样总共需要三个字节;所以这个结构体的大小是3个字节;
所以最后的结构就是3*3+3 = 9;这里面注意宏替换时,参数必须紧挨着参数列表,不然会被解释成两部分。 

  
    
    
  
  1. struct tagAAA
  2. {
  3. unsigned char a : 1;
  4. unsigned char b : 2;
  5. unsigned char c : 6;
  6. unsigned char d : 4;
  7. unsigned char e;
  8. unsigned char f : 4;
  9. unsigned char g;
  10. }AAA_s;
  11. int main()
  12. {
  13. struct tagAAA ;
  14. printf("%d\n", sizeof(struct tagAAA));
  15. system("pause");
  16. return 0;
  17. }
一字节对齐时按照char型来计算,四字节时按照int型来计算;
按照char型,前面位域的分析;a占1bit,还剩7bit,然后把b放进去,还剩5bit,c此时是6bit,不够,开辟一个新的字节;第二个字节还剩2bit,d占4bit需要重新开辟;e表示单独一个完整的字节;f占4bit,第五个字节还剩4bit;g是一个完整的字节;所以总共需要6字节;
按照int型分析:一个int有32位,所以a,b,c,d占一个字节,e是一个完整的字节,f需要单独开辟一个新字节;g表示一个完整的字节;
所以结果是AAA_s在1字节(char)和四字节(int)情况下,占用的字节大小分别是6和16



  
    
    
  
  1. #pragma pack(4)
  2. int main()
  3. {
  4. struct tagPIM
  5. {
  6. unsigned char a;
  7. unsigned char b : 1;
  8. unsigned char c : 2;
  9. unsigned char d : 3;
  10. }*pstdata;
  11. unsigned char puc[4];
  12. pstdata = (struct tagPIM*)puc;
  13. memset(puc,0, 4);
  14. pstdata->a = 2;
  15. pstdata->b = 3;
  16. pstdata->c = 4;
  17. pstdata->d = 5;
  18. printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);
  19. system("pause");
  20. return 0;
  21. }
C语言常见陷阱之“纠缠不清的位域”
C语言常见陷阱之“纠缠不清的位域”

整个结构体占2字节,a是一个完整的字节,b表示位域 从最低位存开始,只占1位,c表示位域从最低位开始占两个位00,d占三个位,从最低位开始取三个位为101;其他的补零。
注意:此平台是小端,存储方式按照,最低位放低地址,最高位放高地址。
这样0x表示是16进制,所以此时很容易得到结果。

本文章来源网络,如果原作者不支持咱们转发,请联系删除,谢谢!

喜欢本文的亲们欢迎点赞



 技术源于积累,成功来自执着

——单片机精讲吴鉴鹰