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

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

__asm__("assembly code");

例如

__asm__("nop");

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

__asm__("movl $1, %eax\n\t"
"movl$4, %ebx\n\t"
"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\n\t"
"movl%%eax, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
printf("Result:%d, %d\n", 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\n\t"
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\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);

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

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

C语言的本质(32)——C语言与汇编之C语言内联汇编的更多相关文章

  1. C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质

    事情的经过是这种,博主在用C写一个简单的业务时使用递归,因为粗心而忘了写return.结果发现返回的结果依旧是正确的.经过半小时的反汇编调试.证明了我的猜想,如今在博客里分享.也是对C语言编译原理的一 ...

  2. 通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制

    通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制 前言说明 本篇为网易云课堂Linux内核分析课程的第四周作业,我将通过调用C语言的库函数与在C代码中 ...

  3. Ok6410裸机驱动学习(三)C语言内嵌汇编

    1.C语言内嵌汇编使用方法 C内嵌汇编以关键字”_asm_或asm开始,下辖4个部分,各部分之间用“:”分开,第一部分是必须写的,后面3个部分可以省略,但是分号:不能省略 优化后的代码 2.汇编程序框 ...

  4. C语言的本质(3)——整数的本质与运算

    C语言的本质(3)--整数的本质与运算 计算机存储的最小单位是字节(Byte),一个字节通常是8个bit.C语言规定char型占一个字节的存储空间.如果这8个bit按无符号整数来解释,则取值范围是0~ ...

  5. C语言的本质(28)——C语言与汇编之用汇编写一个Helloword

    为了更加深入理解C语言的本质,我们需要学习一些汇编相关的知识.作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但是非常重要.因为它能够完成许多其它语言所无法完成的功能.就拿 Linux 内核 ...

  6. C语言的本质(15)——C语言的函数接口入门

    C语言的本质(15)--C语言的函数接口 函数的调用者和其实现者之间存在一个协议,在调用函数之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能. 函数接口通过函数名,参数和返 ...

  7. C语言的本质(7)——C语言运算符大全

    C语言的本质(7)--C语言运算符大全 C语言的结合方向 C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左).例如算术运算符的结合性是自左至右,即先左后右.如有表达式 x- ...

  8. C语言的本质(4)——浮点数的本质与运算

    C语言的本质(4)--浮点数的本质与运算 C语言规定了3种浮点数,float型.double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长 ...

  9. C语言的本质(33)——GCC编译器入门

    GCC(GNU CompilerCollection,GNU编译器套装),是由 GNU 开发的编程语言编译器.它是以GPL许可证所发行的自由软件,也是 GNU计划的关键部分.GCC原本作为GNU操作系 ...

随机推荐

  1. Prime Palindrome Golf

    Prime Palindrome Golf Do you know how to play Prime Palindrome Golf? You are given a number and your ...

  2. 转:C# 定时任务实现

    原文地址:http://blog.csdn.net/Netself/article/details/5766398 C#实现的定时任务类,核心代码如下: 以下代码可直接封装成 TimerTask.dl ...

  3. cf448B Suffix Structures

    B. Suffix Structures time limit per test 1 second memory limit per test 256 megabytes input standard ...

  4. Horner规则

    霍纳(Horner)规则是采用最少的乘法运算策略,求多项式 A(x) = a[n]x^n + a[n-1]x^(n-1) + ... + a[1]x^1 + a[0]x^0 在x处的值. 该规则为 A ...

  5. hbase的thriftserver开启

    说明:hbase的thriftserver默认已经编译好,可以使用,不需要跟hadoopthrift一样配置. 要使用Hbase的thrift接口,必须将它的服务启动,命令行为: hbase-deam ...

  6. Promise 异步执行的同步操作

    Promise 是用来执行异步操作的. 但有时一个异步操作需要等其他的异步操作完成,这时候就可以使用then来做. function loadImageAsync(url) { return new ...

  7. [React] Styling a React button component with Radium

    React's inline styles allow components to stand on their own by not requiring any external CSS. Howe ...

  8. [RxJS] Using Observable.create for fine-grained control

    Sometimes, the helper methods that RxJS ships with such as fromEvent, fromPromise etc don't always p ...

  9. 获取被选择的radio的值

    function selectRadio() { var val = $('input:radio[name="address_select"]:checked').val(); ...

  10. linq和lamda表达式中添加时间判断时解决方案

    在工作中遇到个问题,在使用lamda查询数据的时候,需要添加一个时间判断, DateTime.AddDays(3) > e.ExpirationDate 例如:list = Context.Vo ...