64位下的CPU因为新增了不少寄存器,因此不开启任何优化级别的G++都开始使用寄存器传递函数参数了,好比 printf() 函数使用的就是寄存器传递参数
因此若是必定要之外联汇编的形式优化程序的,那么估计又要记住参数传递使用寄存器规则函数
索性使用内联汇编的形式优化,更加天然优化
G++中的内联汇编分为基本形式的内联汇编与扩展形式的内联汇编;毫无疑问,扩展形式的内联汇编更加复杂,也更增强大spa
二者是同样的,只不过ANSI C标准将asm做为关键字用于其余用途;因此为了与ANSIC兼容,仍是使用__asm__;code
告诉编译器,此处禁止优化,与__asm__一同使用,表示不优化内联汇编段内存
可使用宏定义,来更加方便得使用内联汇编,如编译器
#define _mBeginASM __asm__ __volatile__ ( #define _mEndASM );
基本形式的内联汇编语法如:io
_mBeginASM "汇编代码" _mEndASM
G++会将"汇编代码"部分逐字插入到为程序生成的汇编代码中,因此应该在每一条指令后面手动添加'\n\t';如asm
#include<stdio.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); int a=44; int b=33; int c; int main(int argc,char *argv[]){ _mBeginASM "movl a,%eax\n\t" "addl b,%eax\n\t" "movl %eax,c" _mEndASM printf("%d\n",c);/* 运行能够看出c为77 */ return 0; } /* 使用 g++ -S -o Hello.s Hello.cpp 查看编译器生成的汇编文件*/ /* 全局变量就是直接定义在 .data 段中的数据,并且编译器对变量名并无使用名称修饰 */ .data .align 4 .type a, @object .size a, 4 a: .long 44 .globl b .align 4 .type b, @object .size b, 4 b: .long 33 .globl c .bss .align 4 .type c, @object .size c, 4 c: .zero 4 /* 生成的内联汇编 */ #APP # 15 "Hello.cpp" 1 movl a,%eax addl b,%eax movl %eax,c # 0 "" 2 #NO_APP /* 为了验证G++是将"汇编代码"逐字插入到为程序生成的汇编代码中,能够试着去掉每一条汇编指令中的'\n\t'; */
语法形式:编译
_mBeginASM "汇编代码模板" :输出参数列表 :输入参数列表 :改动的寄存器列表 _mEndASM
总的做用机理就是:模板
g++ 首先根据'输入参数列表'部分将输入参数复制到指定的寄存器(使用mov,fld..等指令);
替换一下汇编代码模板中的占位符,而后将该部分逐字插入到为程序生成的汇编代码中
根据'输出参数列表'部分将指定寄存器中的值mov到C++变量中
感受就像一个函数调用,'汇编代码模板'就是函数体,'输入参数列表'部分指定了函数的参数,'输出参数列表'部分指定了函数的返回值
与基本形式的内联汇编类似,除了多了占位符(%0,%1,...表示着输入参数输出参数),寄存器以前要用两个%%(为了与占位符区分),其余也没什么
若是有多个输入,输出参数,则相互之间用','隔开;每个参数形式如
/* 对于输入参数 */ "描述符"(C++表达式/C++局部|全局变量) /* 对于输出参数 */ "描述符"(左值)
对于输入参数来讲,描述符只有一个字符,用于说明在调用'函数体'以前,应该将变量复制到哪里;对于输出参数来讲,描述符通常有两个字符,说明告终束调用后,从哪里对变量赋值;如:
#include<stdio.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); int main(int argc,char *argv[]){ int a=44,b=33,c; _mBeginASM "addl %%ebx,%%eax" :"=a"(c) /* 说明了调用'函数体'以后,应该把eax中的值赋值该变量c */ :"b"(b),"a"(a) /* 代表了在调用'函数体'以前,应该把变量a复制到eax中,b复制到ebx中 */ _mEndASM printf("%d\n",c); return 0; } /* 生成的汇编代码段 */ movl $44, -28(%rbp) movl $33, -24(%rbp) /* 变量a存放在 -28(%rbp) 中,b 存放在-24(%rbp)中 */ movl -24(%rbp), %ebx /* b->ebx */ movl -28(%rbp), %eax /* a->eax */ #APP /* 调用函数体... */ # 16 "Hello.cpp" 1 addl %ebx,%eax # 0 "" 2 #NO_APP movl %eax, -20(%rbp) /* eax->c;c 存放在-20(%rbp) */
完整的描述符表能够自行搜索...
r 表示任何可用的通用寄存器;
m 表示直接使用内存位置,即内存操做数
_mBeginASM "imul %1,%2\n\t" "movl %2,%0" :"=r"(c) :"r"(a),"r"(b) _mEndASM
做用机理就是:
r表示任何可用的通用寄存器,因此G++首先会为a,b,c选择一个通用寄存器;如对于a选择使用 eax;b选择使用ebx;c选择使用ecx;那么
%0 就是 ecx
%1 就是 eax
%2 就是 ebx
生成的汇编代码以下:
movl -24(%rbp),eax movl -28(%rbp),ebx #APP imul %eax,%ebx movl %ebx,%ecx #NO_APP movl %ecx,-20(%rbp)
_mBeginASM "imul %1,%2" :"=r"(c) :"r"(a),"0"(b) /* "0"(b)表示为b分配的寄存器与%0(即c)同样,即 %2==%0 */ _mEndASM
#include<stdio.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); int main(int argc,char *argv[]){ int a=44,b=33,c; printf("%d\t%d\n",a,b); _mBeginASM "xchgl %1,%2" :"=r"(a) :"0"(a),"m"(b) _mEndASM printf("%d\t%d",a,b); return 0; } /* 生成的汇编代码: */ movl -4(%rbp), %eax /* a->eax */ #APP # 15 "Hello.cpp" 1 xchgl %eax,-8(%rbp) /* -8(%rbp)就是b,m的意思就是使用内存操做数 */ # 0 "" 2 #NO_APP movl %eax, -4(%rbp) /* eax->a */ /* 因为b使用内存操做数,因此不须要在调用以前把b移到某个寄存器中,也不须要在调用以后从某个寄存器中为b赋值 */
描述符,输出值不能用f来描述;
f 表示任何可用的浮点寄存器,即st(0)-st(7);
t 表示 st(0)
u 表示st(1)
#include <stdio.h> #include <math.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); #define _mGetSinCos(d,sind,cosd) \ /* 内联汇编一般与宏连载一块儿 */ do{ \ _mBeginASM \ "fsincos" \ :"=t"(cosd),"=u"(sind) \ :"0"(d) \ _mEndASM \ }while(0); int main(int argc,char *argv[]){ double sind,cosd; double d; while(scanf("%lf",&d) >= 1){ printf("%f\t%f\n",sin(d),cos(d)); _mGetSinCos(d,sind,cosd); printf("%f\t%f\n",sind,cosd); } return 0; }
注意:不要遗忘被修改的FPU寄存器
由于FPU的数据寄存器是以堆栈的形式工做的,因此不要忘记被修改的寄存器,如:
_mBeginASM "fild %1\n\t" "fimul %1\n\t" "fldpi\n\t" "fmul %%st(1),%%st(0)\n\t" :"=t"(area) :"m"(r) :"st(1)" /* 手动运行一下内联汇编指令的话,能够发现退出'函数体'时,st(1)被修改了 * 而且st(1)并无出如今输入,输出列表中,因此应该在'改动的寄存器列表'部分声明一下 */ _mEndASM
若是咱们在内联汇编代码中,修改了某些寄存器的值,而且这些寄存器没有在输入,输出部分出现;那么咱们应该在改动的寄存器列表中登记一下该寄存器
#include<stdio.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); int main(int argc,char *argv[]){ int a=44,b=33,c; printf("%d\t%d\n",a,b); _mBeginASM "movl %2,%%r8d\n\t" "movl %3,%2\n\t" "movl %%r8d,%3" :"=r"(a),"=r"(b) :"0"(a),"1"(b) :"r8" _mEndASM /* 由于r8d被修改了,也即r8被修改了,因此要在'改动的寄存器列表'里声明一下 * 实际上..g++提示:r8d是未知的寄存器名,不过r8能够.. */ printf("%d\t%d",a,b); return 0; }
不过若是寄存器已经在输入,输出部分出现过一次了,则不须要在改动的寄存器列表中从新声明;
条件跳转指令与无条件跳转指令都容许指定一个数字加上方向标志做为标签;处理器会根据方向标志向先后向后搜索,第一个遇到的数字标签会被采用。如:
#include <stdio.h> #include <math.h> #define _mBeginASM __asm__ __volatile__ ( #define _mEndASM ); #define _mMax(a,b,r) \ do{ \ _mBeginASM \ "cmp %1,%2\n\t" \ "jge 0f\n\t" \ "mov %1,%0\n\t" \ "jmp 1f\n" \ "0:\n\t" \ "mov %2,%0\n" \ "1:"\ :"=r"(r) \ :"r"(a),"r"(b) \ _mEndASM \ }while(0); int main(int argc,char *argv[]){ int a,b; int c; while(scanf("%d %d",&a,&b) >= 2){ _mMax(a,b,c); printf("%d\n",c); } return 0; } /* 生成的指令 */ movl -12(%rbp), %eax movl -8(%rbp), %edx #APP # 27 "Hello.cpp" 1 cmp %eax,%edx jge 0f mov %eax,%eax jmp 1f 0: mov %edx,%eax 1: # 0 "" 2 #NO_APP movl %eax, -4(%rbp)
注意:
因为G++是直接将内联汇编插入到为程序生成的汇编代码中,因此标签'1'...
'f'表示向前搜索,'b'表示向后搜索;