zoukankan      html  css  js  c++  java
  • C语言的本质(32)——C语言与汇编之C语言内联汇编


    用C写程序比直接用汇编写程序更简洁,可读性更好,但效率可能不如汇编程序,因为C程序毕竟要经由编译器生成汇编代码,尽管现代编译器的优化已经做得很好了,但还是不如手写的汇编代码。另外,有些平台相关的指令必须手写,在C语言中没有等价的语法,因为C语言的语法和概念是对各种平台的抽象,而各种平台特有的一些东西就不会在C语言中出现了,例如x86是端口I/O,而C语言就没有这个概念,所以in/out指令必须用汇编来写。

    C语言简洁易读,容易组织规模较大的代码,而汇编效率高,而且写一些特殊指令必须用汇编,为了把这两方面的好处都占全了,gcc提供了一种扩展语法可以在C代码中使用内联汇编(Inline Assembly)。最简单的格式是

    __asm__("assembly code");

    例如

    __asm__("nop");

    nop 这条指令什么都不做,只是让CPU空转一个指令执行周期。如果需要执行多条汇编指令,则应该用 将各条指令分隔开,例如:

    __asm__("movl $1, %eax
    	"
             "movl$4, %ebx
    	"
             "int$0x80");

    通常 C 代码中的内联汇编需要和C的变量建立关联,需要用到完整的内联汇编格式:

    __asm__(assembler template
             :output operands                  /*optional */
             :input operands                   /*optional */
             :list of clobbered registers      /*optional */
             );

    这种格式由四部分组成,第一部分是汇编指令,和上面的例子一样,第二部分和第三部分是约束条件,第二部分指示汇编指令的运算结果要输出到哪些C操作数中,C操作数应该是左值表达式,第三部分指示汇编指令需要从哪些C操作数获得输入,第四部分是在汇编指令中被修改过的寄存器列表,指示编译器哪些寄存器的值在执行这条__asm__语句时会改变。后三个部分都是可选的,如果有就填写,没有就空着只写个:号。例如:

    #include <stdio.h>
     
    int main(void)
    {
           int a = 10, b;
     
             __asm__("movl%1, %%eax
    	"
                       "movl%%eax, %0
    	"
                       :"=r"(b)        /* output */
                       :"r"(a)         /* input */
                       :"%eax"         /* clobbered register */
                       );
             printf("Result:%d, %d
    ", a, b);
             return0;
    }

    这个程序将变量a的值赋给b。"r"(a)指示编译器分配一个寄存器保存变量a的值,作为汇编指令的输入,也就是指令中的%1(按照约束条件的顺序,b对应%0,a对应1%),至于%1究竟代表哪个寄存器则由编译器自己决定。汇编指令首先把%1所代表的寄存器的值传给eax(为了和%1这种占位符区分,eax前面要求加两个%号),然后把eax的值再传给%0所代表的寄存器。"=r"(b)就表示把%0所代表的寄存器的值输出给变量b。在执行这两条指令的过程中,寄存器eax的值被改变了,所以把"%eax"写在第四部分,告诉编译器在执行这条__asm__语句时eax要被改写,所以在此期间不要用eax保存其它值。

      

    上面的程序完成将变量a的值赋予变量b,有几点需要说明:

    1、变量b是输出操作数,通过%0来引用,而变量a是输入操作数,通过%1来引用。

    2、输入操作数和输出操作数都使用r进行约束,表示将变量a和变量b存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符'='。

    3、在内联汇编语句中使用寄存器eax时,寄存器名前应该加两个'%',即%%eax。内联汇编中使用%0、%1等来标识变量,任何只带一个'%'的标识符都看成是操作数,而不是寄存器。

    内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。

    4、由于变量b被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。

    5、在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上'%'作为前缀就可以了。

    我们看一下这个程序的反汇编结果:

           __asm__("movl %1, %%eax
    	"
     80483dc:      8b 55 f8                mov    -0x8(%ebp),%edx
     80483df:      89 d0                   mov    %edx,%eax
     80483e1:      89 c2                   mov    %eax,%edx
     80483e3:      89 55 f4                mov    %edx,-0xc(%ebp)
                    "movl %%eax, %0
    	"
                    :"=r"(b)        /* output */
                    :"r"(a)         /* input */
                    :"%eax"         /* clobbered register */
                    );

    可见%0和%1都代表edx寄存器,首先把变量a(位于ebp-8的位置)的值传给edx然后执行内联汇编的两条指令,然后把edx的值传给b(位于ebp-12的位置)。

    有关内联汇编,通常情况下只需要了解这些就足够了。

  • 相关阅读:
    Go
    Go
    Go
    Go
    Go
    Go
    爬虫常用相关库
    Go
    python基础第7天(day19)
    python基础第五天(day17)元组,集合,字符串操作 字符编码:
  • 原文地址:https://www.cnblogs.com/new0801/p/6177048.html
Copyright © 2011-2022 走看看