内联汇编:在C/C++代码中嵌入汇编代码。
汇编的用武之地:
- 效率依旧比C高。
- 有特殊的指令必须用汇编,在C中没有等价的语法。
(1) 内嵌汇编的格式
在GNU下,在高级语言中嵌汇编语言用关键字asm来实现,简单的格式如下:
asm( “assembly code” );
- asm用来标识汇编代码段。括号内的汇编代码必须在引号之内。
- 如果asm模块内有多条语句,若每条语句独占一行则需要将每条语句都用双引号引起来。并且每条语句的末尾需要有换行。
C中嵌汇编的例子,记录C中嵌汇编的格式:
1 #include <stdio.h> 2 3 int main(void) 4 { 5 //AT&T asm systemcall: exit 6 asm( "movl $1, %eax\n\t" 7 "movl $0,%ebx\n\t" 8 "int $0x80"); 9 10 //Print string on screen 11 printf("Hello ""world"" C\n"); 12 13 return 0; 14 } |
- 6到8行是用GNU内敛汇编的格式编写的汇编代码。这些汇编代码表示系统调用exit函数,让程序退出。
- asm括号内有多行汇编代码时,没行代码都需要括起来且需要以\n\t结束。如果不加\n\t的话下一行的首字母会被连接到上一行的末尾(这个是双引号的用法)。关于加\n\t的机制,可能在windows之上不一样。
- 如果,内敛汇编成功,那么编译并执行此C语言程序是不会输出字符串的。
(2)内敛汇编代码访问C的全局变量
在C中内嵌的汇编代码能够处理在C中的变量,可惜只能处理全局变量(已验证)。以下笔记示例:
1 #include <stdio.h> 2 3 int a; 4 int b; 5 int c; 6 7 int main(void) 8 { 9 a = b = 1; 10 c =0; 11 //AT&T asm system call: exit 12 asm( "movl a,%eax\n\t" 13 "movl b, %ebx\n\t" 14 "addl %eax,%ebx\n\t" 15 "movl %ebx, c"); 16 17 //Print string on screen 18 printf("Result is %d\n", c); 19 20 return 0; 21 } |
- 12行至15行代码是内敛的汇编代码。用来处理全局变量a, b, c。在内敛汇编中处理C中的全局变量按照汇编的规则直接引用变量的名称即可。
- 内嵌汇编代码的C程序编译跟编译纯C代码一样。结果输出C的值为2。
(3)内敛汇编扩展
使用以上的内敛汇编代码有一定的局限性,比如在内敛汇编代码中只能访问C的全局变量。而在扩展内敛汇编代码内就能够让内敛汇编代码访问C的任何变量。GNU的扩展内敛汇编笔记如下。
[1]扩展内敛汇编格式
asm (“assembly code”
:output locations
:input operands
:change dregisters);
- assembly code: 跟前面笔记的内敛汇编代码具一样的格式。
- output locations:指示汇编指令的运算结果要输出到哪些C操作数中,C操作数应该是左值表达式。
- input operands:第三部分指示汇编指令需要从哪些C操作数获得输入。
- changed registers:第四部分是在汇编指令中被修改过的寄存器列表,指示编译器哪些寄存器的值在执行这条asm指令后会改变.
对于后三块,没有的模块可以直接省略。非最后一部分省略时冒号不可被省略。
output locations,input operands具有的格式:
“constraint1”(variable1),“constraint2”(variable2)…
- constraint定义variable将被置于何处。[可查看《Professional Assembly language》 page402]
- variable是在C中定义的变量。
[2]使用扩展内敛汇编
还是针对于以上的内敛汇编代码,将全局变量改为局部变量,用扩展内敛汇编去访问C中的变量。
<1>寄存器
1 #include <stdio.h> 2 3 4 int main(void) 5 { 6 int a, b, c; 7 8 a = b = 1; 9 c =0; 10 //AT&T asm system call: exit 11 asm( "movl $0,%%eax\n\t" 12 "addl %%ebx,%%ecx\n\t" 13 "addl %%ecx,%%eax\n\t" 14 : "=a"(c) 15 : "b"(a),"c"(b) 16 ); 17 18 //Print string on screen 19 printf("Result is %d\n", c); 20 21 return 0; 22 } |
- 定义局部变量a, b, c供扩展内敛汇编代码访问。
- 11到16行是扩展内敛汇编代码。针对之前提到的笔记对应解释这段内敛汇编代码。
- 14对应output locations。”a”表示eax寄存器,”c”表示程序中定义定义的变量c。在output locations限制内存处要加“=”。即经此段内敛内敛代码处理后,寄存器eax的值将赋值给变量c。
- 15对应input operands,”b”、”c”分别表示ebx、ecx寄存器。”a”、”b”分表代表程序中的变量b、c。此语句表面,将程序中的变量值b、c分别放在寄存器ebx、ecx中。
- 11行到13行的汇编代码依次表示初始化eax为0,将ebx与ecx内的值相加后放在ecx中,将ecx和eax内的值相加后放在eax中。
用寄存器来作为C变量的存储地,再经汇编代码处理。这个过程其实就是将变量的值拷贝到各寄存器中,然后操作各寄存器,再将作为输出的变量重新赋值。不过为什么在这种情况下要在寄存器前面加两个%应该与%本身功能相关,点到为止,遇到再议。
<2>占位符
当需要内敛汇编代码处理的代码数量较多时,可能会有不知道用哪个寄存器来保存输入变量才比较,恐怕有心有余而力不足的赶脚。使用占位符可解决此烦恼。
占位符用除寄存器外的其它constraint[可查看《Professional Assembly language》page402]来表示。如:
asm( "addl %1, %2\n\t" "addl %2, %0\n\t" : "=r"(c) : "r"(a), "r"(b) ); |
- r只能表示任何可用的通用寄存器。
- %0代表存c变量的地方。
- %1代表存a变量的地方。
- %2代表存b变量的地方。
- 故而汇编代码的操作就是操作%0, %1, %2了,能得到相同的效果。
使用占位符的情况还可以如此:
asm ( “addl %[value1], %[value2]” : [value2] “=r” (data2) :[value1] “r”(data1)); |
<3>内存空间
内存空间的限制符为m。在使用内存空间时,需要用一个寄存器来作为中间存储。
还有其它形式的扩展内敛汇编,形体都差不多。在实际编程中可根据需求和爱好进行选择。
(4)内敛汇编的标识符
[1]asm volatile(“assembly code”);
在asm之后加了volatile之后,表明括号内的汇编代码不被优化。这些是volatile关键字的作用,道理深着。
[2]__asm_
在GNU下可使用asm来表示内敛的汇编代码。如果是在ANSC C中编写内敛汇编代码,需要用__asm_来替换asm。同时volatile也需要换成__volatile_。
(5)内敛汇编用途总结
在C/C++中使用内敛汇编的很多形式为宏。即将内敛汇编定义成宏供C/C++代码调用。将内敛汇编定义成宏的形式更加方便。定义宏的方式跟在C中定义C宏的方法一致,只是宏的形式跟以上笔记的内敛汇编的形式一致。
CNote Over。