g++ 內聯彙編 外聯彙編

外聯彙編

64位下的CPU因爲新增了不少寄存器,因此不開啓任何優化級別的G++都開始使用寄存器傳遞函數參數了,好比 printf() 函數使用的就是寄存器傳遞參數
因此若是必定要之外聯彙編的形式優化程序的,那麼估計又要記住參數傳遞使用寄存器規則函數

索性使用內聯彙編的形式優化,更加天然優化

內聯彙編

G++中的內聯彙編分爲基本形式的內聯彙編擴展形式的內聯彙編;毫無疑問,擴展形式的內聯彙編更加複雜,也更增強大spa

__asm__與asm

二者是同樣的,只不過ANSI C標準將asm做爲關鍵字用於其餘用途;因此爲了與ANSIC兼容,仍是使用__asm__;code

__volatile__於volatile

告訴編譯器,此處禁止優化,與__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賦值 */


使用FPU

描述符,輸出值不能用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'表示向後搜索;

相關文章
相關標籤/搜索