内联汇编:在C/C++代码中嵌入汇编代码。
汇编的用武之地:
效率依旧比C高。
有特殊的指令必须用汇编,在C中没有等价的语法。
(1) 内嵌汇编的格式
在GNU下,在高级语言中嵌汇编语言用关键字asm来实现,简单的格式如下:
asm( “assembly code” );
asm用来标识汇编代码段。括号内的汇编代码必须在引号之内。
如果asm模块内有多条语句,若每条语句独占一行则需要将每条语句都用双引号引起来。并且每条语句的末尾需要有换行。
C中嵌汇编的例子,记录C中嵌汇编的格式:
1 #include
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
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)…
onstraint定义variable将被置于何处。[可查看《Professional Assembly language》page402]
variable是在C中定义的变量。
[2]使用扩展内敛汇编
还是针对于以上的内敛汇编代码,将全局变量改为局部变量,用扩展内敛汇编去访问C中的变量。
<1>寄存器
1 #include
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限制内存处要加“=”。即经此段内敛内敛代码处理后,C语言变量c的值存在a所代表的的内存里。
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。