Chinaunix首页 | 论坛 | 博客
  • 博客访问: 17450
  • 博文数量: 3
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 13
  • 用 户 组: 普通用户
  • 注册时间: 2015-04-07 16:24
个人简介

There is noting in my head!

文章分类

全部博文(3)

文章存档

2016年(1)

2015年(2)

我的朋友

分类: C/C++

2015-04-09 21:52:29

首先,如果你不知道位段(也叫位域)的概念,请自行学习,本文不再赘述




    OK,废话不多说,先上测试代码:

  1. #include <stdio.h>

  2. struct test {
  3.     int a:4;    //4 bit
  4.     int b:10;    //10 bit
  5.     int c:6;     //6 bit
  6.     int d:12;    //12 bit
  7. }tmp;

  8. int main(void)
  9. {
  10.     tmp.a = 11;
  11.     tmp.b = 12;
  12.     tmp.c = 13;
  13.     tmp.d = 14;

  14.     printf("sizeof = %d\n", sizeof(struct test));

  15.     return 0;
  16. }

首先,定义一个结构体变量tmp,该结构体包含四个位段元素,主函数分别对这个结构体变量的4个成员赋值,接下来我们使用GCC将该段代码编译成汇编文件。

  1. yunyafeng@x3650-m4-ip202:~$ vim test.c
  2. yunyafeng@x3650-m4-ip202:~$ gcc -S test.c
  3. yunyafeng@x3650-m4-ip202:~$


生成的汇编文件代码如下:

点击(此处)折叠或打开

  1.     .file    "test.c"
  2.     .comm    tmp,4,4        @这个地方就是我们的tmp变量,一共占4个字节的空间
  3.     .section    .rodata
  4. .LC0:
  5.     .string    "sizeof = %d\n"
  6.     .text
  7.     .globl    main
  8.     .type    main, @function
  9. main:                        @main函数开始
  10. .LFB0:
  11.     .cfi_startproc
  12.     pushl    %ebp
  13.     .cfi_def_cfa_offset 8
  14.     .cfi_offset 5, -8
  15.     movl    %esp, %ebp
  16.     .cfi_def_cfa_register 5
  17.     andl    $-16, %esp
  18.     subl    $16, %esp       @在这之前都是main函数执行的准备工作
  19.     
  20.     @整个结构体的赋值是从这里开始
  21.     movzbl    tmp, %eax
  22.     andl    $-16, %eax
  23.     orl    $11, %eax
  24.     movb    %al, tmp
  25.     movzwl    tmp, %eax
  26.     andw    $-16369, %ax
  27.     orb    $-64, %al
  28.     movw    %ax, tmp
  29.     movl    tmp, %eax
  30.     andl    $-1032193, %eax
  31.     orl    $212992, %eax
  32.     movl    %eax, tmp
  33.     movzwl    tmp+2, %eax
  34.     andl    $15, %eax
  35.     orb    $-32, %al
  36.     movw    %ax, tmp+2
  37.     @结构体赋值到此结束

  38.     movl    $4, 4(%esp)
  39.     movl    $.LC0, (%esp)
  40.     call    printf
  41.     movl    $0, %eax
  42.     leave
  43.     .cfi_restore 5
  44.     .cfi_def_cfa 4, 4
  45.     ret
  46.     .cfi_endproc
  47. .LFE0:
  48.     .size    main, .-main
  49.     .ident    "GCC: (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1"
  50.     .section    .note.GNU-stack,"",@progbits

首先我们应该清楚tmp这个变量在内存中到底是则么存的,看下图:
为了方便我们用以下四种颜色(不要问我什么颜色 我也不认识 能看到就好)分别代表tmp中的 a ,b, c, d元素。
初始值我们也不知道是多少就用x表示
31  (高地址)----------->
d d d d d d d d d d d d c c c c c c b b b b b b b b b b a a a a
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
构体的第1个元素位于最低地址处,依次类推。


好的 我们接下来一步一步的分析,首先是结构体中的第1个元素tmp.a的赋值过程;
  1.     movzbl tmp, %eax
  2.     andl $-16, %eax
  3.     orl $11, %eax
  4.     movb %al, tmp

这4行代码为第一个元素赋值的过程,其中:
第1条指令:将tmp的第一个字节以高位补0的方式放入eax寄存器中。
    此时:eax值:(00000000 00000000 00000000 xxxx xxxx

第2条指令:将eax中的值与-16(11111111 11111111 11111111 1111 0000)进行位与操作,将结果存入eax中。所以此操作是将eax的最低4位清空。
    此时:eax值:(00000000 00000000 00000000 xxxx 0000

第3条指令:将eax中的值与11进行位或操作,将结果存入eax中。也就是给eax的最低4位中写入11。
    此时:eax值:00000000 00000000 00000000 0000 1011

第4条指令:将al(eax的最低8位:0000 1011)中的值回写到tmp的第一个字节中,此时内存中的值为:


31  (高地址)----------->
d d d d d d d d d d d d c c c c c c b b b b b b b b b b a a a a
x x x x x x x x x x x x x x x x x x x x x x x x 0 0 0 0 1 0 1 1
可见第一条赋值语句tmp.a = 11;执行完成。


第2个元素tmp.b的赋值过程:

点击(此处)折叠或打开

  1.     movzwl tmp, %eax
  2.     andw $-16369, %ax
  3.     orb $-64, %al
  4.     movw %ax, tmp
第1条指令:将tmp的前2个字节以高位补0的方式放入eax寄存器中。
    此时:eax值:(00000000 00000000 xx xxxxxx0000 1011

第2条指令:将ax(eax的低16位)中的值与-16369(11 0000000000 1111)进行位与操作,将结果存入ax中。所以此操作是将eax的4-13位清空。(该条指令操作数长度为16位)
    此时:eax值:(00000000 00000000 xx 0000000000 1011

第3条指令:将al(eax的最低8位)中的值与-64(1100 0000)进行位或操作,将结果存入al中。也就是给eax的4-7位中写入12。(操作数长度为8位)
    此时:eax值:(00000000 00000000 xx 0000001100 1011

第4条指令:将ax(eax的低16位:xx 0000001100 1011)中的值回写到tmp的前2个字节中,此时内存中的值为:
31  (高地址)----------->
d d d d d d d d d d d d c c c c c c b b b b b b b b b b a a a a
x x x x x x x x x x x x x x x x x x 0 0 0 0 0 0 1 1 0 0 1 0 1 1
tmp.b = 12;执行完成。


第3个元素tmp.c的赋值过程:

点击(此处)折叠或打开

  1.     movl tmp, %eax
  2.     andl $-1032193, %eax
  3.     orl $212992, %eax
  4.     movl %eax, tmp
1条指令:将tmp里的值放入eax寄存器中。
    此时:eax值: (xxxxxxxxxxxx xxxxxx 0000001100 1011)

第2条指令:将eax中的值与-1032193(111111111111 000000 1111111111 1111)进行位与操作,将结果存入eax中。所以此操作是将eax的14-19位清空。
     此时:eax值: (xxxxxxxxxxxx 000000 0000001100 1011)

第3条指令:将eax中的值与212992 (000000000000 001011 0000000000 0000)进行位或操作,将结果存入eax中。也就是给eax的14-19位中写入12。(操作数长度为8位)
    此时:eax值: (xxxxxxxxxxxx 001011 0000001100
 1011)

第4条指令:将eax中的值:(xxxxxxxxxxxx 001011 0000001100 1011)写到tmp的内存中,此时内存中的值为:
31  (高地址)----------->
d d d d d d d d d d d d c c c c c c b b b b b b b b b b a a a a
x x x x x x x x x x x x 0 0 1 0 1 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1
tmp.c = 13;执行完成。


第4个元素tmp.d的赋值过程:

点击(此处)折叠或打开

  1.     movzwl    tmp+2, %eax
  2.     andl    $15, %eax
  3.     orb    $-32, %al
  4.     movw    %ax, tmp+2
1条指令:将tmp+2的(tmp的最高16位)的值以高位补0的方式放eax寄存器中。
    此时:eax值: 
(00000000 00000000 xxxxxxxxxxxx 0010)

第2条指令:将eax中的值与15(00000000 00000000 000000000000 1111)进行位与操作,将结果存入eax中。所以此操作是将eax的5- 31位清空。
     此时:eax值: (00000000 00000000 000000000000
 1111)

第3条指令:将eax中的值与-32 (1110 0000)进行位或操作,将结果存入eax中。也就是给eax的4-7位中写入14。(操作数长度为8位)
 此时:eax值: (00000000 00000000 000000001110 0010

第4条指令:将ax(eax的低16位: 000000001110 0010)中的值回写到tmp+2地址处(tmp的高16位),此时内存中的值为:
31  (高地址)----------->
d d d d d d d d d d d d c c c c c c b b b b b b b b b b a a a a
0 0 0 0 0 0 0 0 1 1 1 0 0 0 1 0 1 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1
tmp.d = 14;执行完成。 

到此结构体的所有成员赋值完毕。

总结:
    linux下, gcc对于位段的实现是靠位运算完成的,一个位段的赋值过成需要分解成多条指令来完成。可见位段的使用其实是节省了内存空间,但是节省空间是靠牺牲速度换来的。所以对于位段的使用还要根据实际情况来决定。


本文地址:http://blog.chinaunix.net/uid-30195520-id-4947212.html





待补充.....(其实原理差不多只不过指令集机不一样,有时间再写)
    


阅读(1445) | 评论(0) | 转发(0) |
3

上一篇:没有了

下一篇:ldm和stm用法详解

给主人留下些什么吧!~~