只有C语言可以内联汇编吗,GCC C语言内联汇编程序

在嵌入式汇编中,您可以将C语言表达式指定为汇编指令的操作数,而不必担心如何将C语言表达式的值读取到寄存器中以及如何将C语言表达式写入寄存器中. 计算结果返回到C变量. 您只需要告诉C语言表达式和程序中汇编指令操作数之间的对应关系,GCC就会自动插入代码以完成必要的操作.

1. 简单的内联汇编

示例:

__ asm__ __volatile __(“ hlt”); “ __asm__”表示以下代码是内联汇编,“ asm”是“ __asm__”的别名. “ __volatile__”表示编译器不会优化代码,下面的说明保持不变,“ volatile”为其别名. 组装说明在括号中.

2. 内联汇编的例子

使用内联汇编,必须首先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC这些操作受到哪些限制. 例如,在以下汇编语句中:

__ asm__ __violate __(“ movl%1,%0”: “ = r”(结果): “ m”(输入));

“电影%1,%0”是指令模板; “%0”和“%1”表示指令的操作数,称为占位符,并且内联汇编程序依靠它们来转换C语言表达式和相应的指令操作数. 指令模板在C语言表达式后括在括号中. 在此示例中,只有两个: “结果”和“输入”,它们按照出现顺序对应于指令操作数“%0”和“%1”. 注意相应的顺序: 第一个C表达式对应于“%0”;第二个表达式对应于“%1”,依此类推,最多有10个操作数,分别使用“%0”和“%1”. ...“%9”表示. 每个操作数前面都有一个用引号引起来的字符串,字符串的内容是操作数的限制或要求. “结果”前面的受限字符串是“ = r”,其中“ =”表示“结果”是输出操作数,“ r”表示“结果”需要与通用寄存器关联,值首先将操作数读入寄存器,然后在指令中使用相应的寄存器,而不是“结果”本身. 当然,在执行指令之后,寄存器中的值需要存储在变量“结果”中. 从表面上看,该指令似乎直接执行“结果”运算,实际上GCC已经执行了隐式处理,因此我们可以编写更少的指令. “输入”前面的“ r”表示需要先将表达式放入寄存器中,然后在指令中使用该寄存器参与操作.

C表达式或变量与寄存器之间的关系由GCC自动处理. 我们只需要使用限制字符串来指示GCC如何处理它. 限制字符必须符合指令的操作数要求,否则生成的汇编代码将是错误的,读者可以将上面示例中的两个“ r”更改为“ m”(m表示操作数位于内存中,而不是寄存器中),编译后的结果是:

移动输入,结果

很明显,这是一条非法指令,因此限制字符串必须符合指令的操作数要求. 例如,指令movl允许寄存器到寄存器,立即数到寄存器等,但不允许内存到内存操作,因此两个操作数不能同时使用“ m”作为限定符.

内联汇编语法如下:

__ asm __(汇编语句模板: 输出部分: 输入部分: 销毁描述部分)

总共四个部分: 汇编语句模板,输出部分,输入部分,销毁描述部分,每个部分用“: ”分隔,汇编语句模板是必不可少的,其他三个部分是可选的(如果后面的部分是可选的)使用,并且前面部分为空,您还需要使用“: ”打开,相应的部分为空. 例如:

__ asm__ __volatile __(“ cli” ::: “内存”)

1. 汇编语句模板

汇编语句模板由汇编语句序列组成,用“;”,“ \ n”或“ \ n \ t”分隔. 指令中的操作数可以使用占位符来引用C语言变量. 操作数最多包含10个占位符,其名称如下: %0,%1,...,%9. 指令中由占位符表示的操作数始终被认为是长整数(4个字节),但是根据指令的不同,应用于它们的操作可以是字或字节. 当使用操作数作为单词或字节时,默认值为低位字或低位字节. 字节操作可以显式指示是低字节还是子字节. 方法是在%和序列号之间插入一个字母,“ b”代表低字节,“ h”代表高字节,例如: %h1.

2. 输出部分

输出部分描述输出操作数. 不同的操作数描述符用逗号分隔. 每个操作数描述符由合格的字符串和C语言变量组成. 每个输出操作数的限定字符串必须包含“ =”,以表明它是一个输出操作数.

示例:

__ asm__ __volatile __(“ pushfl; popl%0; cli”: “ = g”(x))

描述符字符串指示对变量的限制,以便GCC可以根据这些条件决定如何分配寄存器,以及如何生成必要的代码来处理指令操作数与C表达式或C变量之间的关系.

3. 输入部分

输入部分描述输入操作数. 不同的操作数描述符用逗号分隔. 每个操作数描述符都由限定的字符串和C语言表达式或C语言变量组成.

示例1:

__ asm__ __volatile__(“ lidt%0” ::“ m”(real_mode_idt));

示例二(bitops.h):

静态__inline__ void __set_bit(int nr,volatile void * addr)

{

__ asm __(

“ btsl%1,%0”

: “ = m”(ADDR)

: “ Ir”(nr));

}

示例功能是将(* addr)的nr位设置为1. 第一个占位符%0对应于C语言变量ADDR,第二个占位符%1对应于C语言变量nr. 因此,以上汇编语句代码等效于以下伪代码: btsl nr,ADDR,此指令的两个操作数不能全部是存储器变量,因此将nr的合格字符串指定为“ Ir”,并将nr和立即数指定为或关联该寄存器,以便在两个操作数中只有ADDR是存储变量.

只有C语言可以内联汇编吗,GCC C语言内联汇编程序_第1张图片

4. 限制字符

4.1. 受限字符列表

有多种限制字符,其中一些与特定体系结构有关. 这里仅列出i386中可能使用的常用限定词和一些常用限定词. 它们的作用是指导编译器如何处理以下C语言变量与指令操作数之间的关系.

类别预选赛说明

通用寄存器“ a”将输入变量放入eax

这里有一个问题: 如果已经使用了eax,该怎么办?

实际上非常简单: 因为GCC知道已经使用了eax,所以它在此汇编代码中

在语句开头插入一个pushl%eax语句,将eax的内容保存到堆栈中,然后

在此代码的末尾添加一条语句popl%eax以恢复eax的内容

“ b”将输入变量放入ebx

“ c”将输入变量放入ecx

“ d”将输入变量放入edx

“ s”将输入变量放入esi

“ d”将输入变量放入edi

“ q”将输入变量放入eax,ebx,ecx和edx之一

“ r”将输入变量放入通用寄存器,即eax,ebx,ecx,

edx,esi,edi之一

“ A”将eax和edx组合到一个64位寄存器中(使用长整型)

内存“ m”个内存变量

“ o”操作数是一个内存变量,但其寻址方法是偏移类型,

即基址或基址加索引地址

“ V”操作数是一个内存变量,但寻址模式不是偏移类型

“”“操作数是内存变量,但是寻址模式是自动递增

“ p”操作数是合法的内存地址(指针)

寄存器或存储器“ g”将输入变量放入eax,ebx,ecx,edx之一

或作为存储变量

“ X”操作数可以是任何类型

立即

“ I”之间的立即数0-31(用于32位移位指令)

“ J” 0-63之间的立即数(用于64位移位指令)

“ N”立即数介于0-255之间(用于out命令)

“我”立即

“ n”个即时数据,某些系统不支持单词以外的即时数据,

这些系统应使用“ n”代替“ i”

匹配“ 0”表示受其限制的操作数与指定的操作数匹配,

“ 1” ...,即操作数是指定的操作数,例如“ 0”

使用“ 9”来描述“%1”操作数,然后实际上是“%1”引用它

是“%0”操作数c语言对应汇编语句,请注意0-9和

作为限定字母

指令中“%0”和“%9”之间的区别,前者描述了操作数,

后者代表操作数.

&输出操作数不能使用与输入操作数相同的寄存器

操作符类型“ =”操作数仅写在指令(输出操作数)中

“ +”操作数是指令中的读写类型(输入和输出操作数)

浮点数“ f”浮点寄存器

“ t”第一个浮点寄存器

“ u”第二个浮点寄存器

“ G”标准的80387浮点常数

%该操作数可以与下一个操作数交换位置

例如,addl的两个操作数可以交换顺序

(当然,两个操作数都不能是立即数)

注释的#部分,将从该字符到其后的逗号的所有字母都忽略

*表示如果选择了寄存器,则会忽略其后的字母

5. 破坏描述部分

销毁描述符用于通知编译器我们使用的寄存器或内存,并由逗号分隔的字符串组成. 每个字符串描述一个情况,通常是一个寄存器名称. 除寄存器外,还有“内存”. 例如: “%eax”,“%ebx”,“内存”等.

“内存”很特殊,可能是内联汇编中最困难的部分. 为了清楚地解释它,首先介绍编译器的优化知识,然后查看C关键字volatile. 最后,查看描述符.

1. 编译器优化简介

内存访问速度远远小于CPU处理速度. 为了提高计算机的整体性能,将硬件高速缓存引入硬件中以加快对内存的访问. 另外,在现代CPU中,不必严格地按顺序执行指令的执行,并且可以不按顺序执行没有相关性的指令,从而充分利用CPU的指令流水线并提高执行速度. 以上是硬件级别的优化. 从软件级别看优化: 一种是程序员在编写代码时的优化,另一种是编译器的优化. 编译器优化的常用方法是: 将内存变量缓存到寄存器中;调整指令顺序以充分利用CPU指令流水线,通常对读取和写入指令进行重新排序. 优化常规内存时c语言对应汇编语句,这些优化是透明且高效的. 解决由编译器优化或硬件重新排序引起的问题的方法是,在必须从硬件(或其他处理器)角度按特定顺序执行的操作之间设置内存屏障. Linux提供了宏解决方案编译器的执行顺序.

void屏障(void)

此函数通知编译器插入一个内存屏障,但对硬件无效. 编译后的代码会将当前CPU寄存器中的所有修改后的值存储到内存中. 需要这些数据时,将再次从存储器中读取它们.

2. C语言关键字volatile

C语言关键字volatile(请注意,它用于修改变量,而不是上面介绍的__volatile__)表示变量的值可以在外部更改,因此每次都无法将对这些变量的访问缓存在寄存器中-访问时使用. 此关键字通常在多线程环境中使用,因为在编写多线程程序时,同一变量可能会被多个线程修改,并且程序会通过该变量同步每个线程,例如:

DWORD __stdcall threadFunc(LPVOID信号)

{

int * intSignal = reinterpret_cast(signal);

* intSignal = 2;

while(* intSignal!= 1)

sleep(1000);

返回0;

}

当线程启动时,将intSignal设置为2,然后循环等待直到intSignal为1退出. 显然,必须从外部更改intSignal的值,否则该线程将不会退出. 但是该线程在实际运行时不会退出,即使在外部将其值更改为1时,也可以通过查看相应的伪汇编代码来清除该线程:

移动斧头,信号

标签:

if(ax!= 1)

转到标签

对于C编译器,它不知道此值将被其他线程修改. 自然地将其缓存在寄存器中. 记住,C编译器没有线程的概念!这次您需要使用volatile. volatile的原始含义是: 可以在当前线程之外更改此值. 换句话说,我们需要在threadFunc的intSignal前面添加volatile关键字. 此时,编译器知道该变量的值将在外部更改,因此每次访问该变量时都将再次读取该变量,并且循环变为如下伪代码所示:

标签:

移动斧头,信号

if(ax!= 1)

转到标签

3,内存

基于以上知识,不难理解内存修改描述符. 内存描述符告诉GCC:

1)请勿从先前的说明中重新排序内联汇编说明;也就是说,在执行内联汇编代码之前,必须先执行所有指令

2)不要在寄存器中缓存变量,因为此代码可能使用内存变量,并且这些内存变量将以不可预测的方式更改,因此GCC插入必要的代码以首先将变量值缓存在寄存器中到内存,如果以后再访问这些变量,则需要再次访问内存.

本文来自电脑杂谈,转载请注明本文网址:

http://www.pc-fly.com/a/jisuanjixue/article-275394-1.html

你可能感兴趣的:(只有C语言可以内联汇编吗)