java置入_asm汇编代码_C语言ASM汇编内嵌语法详解

GCC内联汇编允许在C/C++代码中嵌入汇编指令,简化高效代码编写。基本内联汇编由`__asm__`关键字和"Instruction List"组成,可以通过`__volatile__`声明不受优化影响。带有C/C++表达式的内联汇编增加了Output、Input和Clobber/Modify部分,用于指定操作和寄存器约束。Output中的`=`和`+`分别表示Write-Only和Read-Write,`&`表示独占寄存器。占位符 `%0`, `%1`等对应Input/Output操作表达式,Clobber/Modify声明可能改变的寄存器或内存。" 103411871,7665252,使用JSP开发成语接龙游戏,"['前端开发', 'Java', 'Web应用', '成语游戏']
摘要由CSDN通过智能技术生成

3 GCC Inline ASM

GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。

1.基本内联汇编

GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:

__asm__("movl %esp,%eax"); // 看起来很熟悉吧!

或者是

__asm__("

movl $1,%eax // SYS_exit

xor %ebx,%ebx

int $0x80

");

__asm__(

"movl $1,%eax\r\t" \

"xor %ebx,%ebx\r\t" \

"int $0x80" \

);

基本内联汇编的格式是

__asm__ __volatile__("Instruction List");

1、__asm__

__asm__是GCC关键字asm的宏定义:

#define __asm__ asm

__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。

2、Instruction List

Instruction List是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。

我们看一看下面这个例子:

$ cat example1.c

int main(int __argc, char* __argv[])

{

int* __p = (int*)__argc;

(*__p) = 9999;

//__asm__("":::"memory");

if((*__p) == 9999)

return 5;

return (*__p);

}

在 这段代码中,那条内联汇编是被注释掉的。在这条内联汇编之前,内存指针__p所指向的内存被赋值为9999,随即在内联汇编之后,一条if语句判断__p 所指向的内存与9999是否相等。很明显,它们是相等的。GCC在优化编译的时候能够很聪明的发现这一点。我们使用下面的命令行对其进行编译:

$ gcc -O -S example1.c

选项-O表示优化编译,我们还可以指定优化等级,比如-O2表示优化等级为2;选项-S表示将C/C++源文件编译为汇编文件,文件名和C/C++文件一样,只不过扩展名由.c变为.s。

我们来查看一下被放在example1.s中的编译结果,我们这里仅仅列出了使用gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性,无关的其它代码未被列出。

$cat example1.s

main:

pushl %ebp

movl %esp, %ebp

movl 8(%ebp), %eax # int* __p = (int*)__argc

movl $9999, (%eax) # (*__p) = 9999

movl $5, %eax # return 5

popl %ebp

ret

参 照一下C源码和编译出的汇编代码,我们会发现汇编代码中,没有if语句相关的代码,而是在赋值语句(*__p)=9999后直接return 5;这是因为GCC认为在(*__p)被赋值之后,在if语句之前没有任何改变(*__p)内容的操作,所以那条if语句的判断条件(*__p) == 9999肯定是为true的,所以GCC就不再生成相关代码,而是直接根据为true的条件生成return 5的汇编代码(GCC使用eax作为保存返回值的寄存器)。

我们现在将example1.c中内联汇编的注释去掉,重新编译,然后看一下相关的编译结果。

$ gcc -O -S example1.c

$ cat example1.s

main:

pushl %ebp

movl %esp, %ebp

movl 8(%ebp), %eax # int* __p = (int*)__argc

movl $9999, (%eax) # (*__p) = 9999

#APP

# __asm__("":::"memory")

#NO_APP

cmpl $9999, (%eax) # (*__p) == 9999 ?

jne .L3 # false

movl $5, %eax # true, return 5

jmp .L2

.p2align 2

.L3:

movl (%eax), %eax

.L2:

popl %ebp

ret

由于内联汇编语句__asm__("":::"memory")向GCC声明,在此内联汇编语句出现的位置内存内容可能了改变,所以GCC在编译时就不能像刚才那样处理。这次,GCC老老实实的将if语句生成了汇编代码。

可能有人会质疑:为什么要使用__asm__("":::"memory")向GCC声明内存发生了变化?明明“Instruction List”是空的,没有任何对内存的操作,这样做只会增加GCC生成汇编代码的数量。

确 实,那条内联汇编语句没有对内存作任何操作,事实上它确实什么都没有做。但影响内存内容的不仅仅是你当前正在运行的程序。比如,如果你现在正在操作的内存 是一块内存映射,映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序,I/O设备也会去操作这块内存。既然两者都会去操作同一块 内存,那么任何一方在任何时候都不能对这块内存的内容想当然。所以当你使用高级语言C/C++写这类程序的时候,你必须让编译器也能够明白这一点,毕竟高 级语言最终要被编译为汇编代码。

你可能已经注意到了,这次输出的汇编结果中,有两个符号:#APP和#NO_APP,GCC将内联汇编语 句中"Instruction List"所列出的指令放在#APP和#NO_APP之间,由于__asm__("":::"memory")中“Instruction List”为空,所以#APP和#NO_APP中间也没有任何内容。但我们以后的例子会更加清楚的表现这一点。

关于为什么内联汇编__asm__("":::"memory")是一条声明内存改变的语句,我们后面会详细讨论。

刚才我们花了大量的内容来讨论"Instruction List"为空是的情况,但在实际的编程中,"Instruction List"绝大多数情况下都不是空的。它可以有1条或任意多条汇编指令。

当 在"Instruction List"中有多条指令的时候,你可以在一对引号中列出全部指令,也可以将一条或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,你可以将 每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(\n,大多数情况下\n后还要跟一个\t,其中\n是为了换行,\t是为了 空出一个tab宽度的空格)将它们分开。比如:

__asm__("movl %eax, %ebx

sti

popl %edi

subl %ecx, %ebx");

__asm__("movl %eax, %ebx; sti

popl %edi; subl %ecx, %ebx");

__asm__("movl %eax, %ebx; sti\n\t popl %edi

subl %ecx, %ebx");

都是合法的写法。如果你将指令放在多对引号中,则除了最后一对引号之外,前面的所有引号里的最后一条指令之后都要有一个分号(;)或(\n)或(\n\t)。比如:

__asm__("movl %eax, %ebx

sti\n"

"popl %edi;"

"subl %ecx, %ebx");

__asm__("movl %eax, %ebx; sti\n\t"

"popl %edi; subl %ecx, %ebx");

__asm__("movl %eax, %ebx; sti\n\t popl %edi\n"

"subl %ecx, %ebx");

__asm__("movl %eax, %ebx; sti\n\t popl %edi;" "subl %ecx, %ebx");

都是合法的。

上述原则可以归结为:

任意两个指令间要么被分号(;)分开,要么被放在两行; 放在两行的方法既可以从通过\n的方法来实现,也可以真正的放在两行; 可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。在基本内联汇编中,“Instruction List”的书写的格式和你直接在汇编文件中写非内联汇编没有什么不同,你可以在其中定义Label,定义对齐(.align n ),定义段(.section name )。例如:

__asm__(".align 2\n\t"

"movl %eax, %ebx\n\t"

"test %ebx, %ecx\n\t"

"jne error\n\t"

"sti\n\t"

"error: popl %edi\n\t"

"subl %ecx, %ebx");

上面例子的格式是Linux内联代码常用的格式,非常整齐。也建议大家都使用这种格式来写内联汇编代码。

3、__volatile__

__volatile__是GCC关键字volatile的宏定义:

#define __volatile__ volatile

__volatile__ 或volatile是可选的,你可以用它也可以不用它。如果你用了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每一条指令”,否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指 令优化掉。

那么GCC判断的原则是什么?我不知道(如果有哪位朋友清楚的话,请告诉我)。我试验了一下,发现一条内联汇编语句如果是基本 内联汇编的话(即只有“Instruction List”,没有Input/Output/Clobber的内联汇编,我们后面将会讨论这一点),无论你是否使用__volatile__来修饰, GCC 2.96在优化编译时,都会原封不动的保留内联汇编中的“Instruction List”。但或许我的试验的例子并不充分,所以这一点并不能够得到保证。

为了保险起见,如果你不想让GCC的优化影响你的内联汇编代码,你最好在前面都加上__volatile__,而不要依赖于编译器的原则,因为即使你非常了解当前编译器的优化原则,你也无法保证这种原则将来不会发生变化。而__volatile__的含义却是恒定的。

2、带有C/C++表达式的内联汇编

GCC允许你通过C/C++表达式指定内联汇编中"Instrcuction List"中指令的输入和输出,你甚至可以不关心到底使用哪个寄存器被使用,完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用,也可以提高目标代码的效率。

我们先来看几个例子:

__asm__ (" " : : :

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值