|
GCC支持在C/C++代碼中嵌入?yún)R編代碼,這些代碼被稱作是"GCC Inline ASM"(GCC內(nèi)聯(lián)匯編); 一、基本內(nèi)聯(lián)匯編 GCC中基本的內(nèi)聯(lián)匯編非常易懂,格式如下: __asm__ [__volatile__] ("instruction list"); 其中, 1.__asm__: 它是GCC定義的關(guān)鍵字asm的宏定義(#define __asm__ asm),它用來聲明一個內(nèi)聯(lián)匯編表達式,所以,任何一個內(nèi)聯(lián)匯編表達式都以它開頭,它是必不可少的;如果要編寫符合ANSI C標準的代碼(即:與ANSI C兼容),那就要使用__asm__; 2.__volatile__: 它是GCC關(guān)鍵字volatile的宏定義;這個選項是可選的;它向GCC聲明"不要動我所寫的instruction list,我需要原封不動地保留每一條指令";如果不使用__volatile__,則當你使用了優(yōu)化選項-O進行優(yōu)化編譯時,GCC將會根據(jù)自己的判斷來決定是否將這個內(nèi)聯(lián)匯編表達式中的指令優(yōu)化掉;如果要編寫符合ANSI C標準的代碼(即:與ANSI C兼容),那就要使用__volatile__; 3.instruction list: 它是匯編指令列表;它可以是空列表,比如:__asm__ __volatile__("");或__asm__("");都是合法的內(nèi)聯(lián)匯編表達式,只不過這兩條語句什么都不做,沒有什么意義;但并非所有"instruction list"為空的內(nèi)聯(lián)匯編表達式都是沒意義的,比如:__asm__("":::"memory");就是非常有意義的,它向GCC聲明:"我對內(nèi)存做了改動",這樣,GCC在編譯的時候,就會將此因素考慮進去; 例如: __asm__("movl %esp,%eax"); 或者是 __asm__("movl $1,%eax xor %ebx,%ebx int $0x80"); 或者是 __asm__("movl $1,%eax\n\t"\ "xor %ebx,%ebx\n\t"\ "int $0x80"); instruction list的編寫規(guī)則:當指令列表里面有多條指令時,可以在一對雙引號中全部寫出,也可將一條或多條指令放在一對雙引號中,所有指令放在多對雙引號中;如果是將所有指令寫在一對雙引號中,那么,相鄰倆條指令之間必須用分號";"或換行符(\n)隔開,如果使用換行符(\n),通常\n后面還要跟一個\t;或者是相鄰兩條指令分別單獨寫在兩行中; 規(guī)則1:任意兩條指令之間要么被分號(;)或換行符(\n)或(\n\t)分隔開,要么單獨放在兩行; 規(guī)則2:單獨放在兩行的方法既可以通過\n或\n\t的方法來實現(xiàn),也可以真正地放在兩行; 規(guī)則3:可以使用1對或多對雙引號,每1對雙引號里面可以放1條或多條指令,所有的指令都要放在雙引號中; 例如,下面的內(nèi)聯(lián)匯編語句都是合法的: __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);比如,下面的內(nèi)聯(lián)匯編語句都是合法的: __asm__("movl %eax,%ebx sti\n" "popl %edi;" "subl %ecx,%bx"); __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"); 二、帶有C/C++表達式的內(nèi)聯(lián)匯編 GCC允許你通過C/C++表達式指定內(nèi)聯(lián)匯編中"instruction list"中的指令的輸入和輸出,你甚至可以不關(guān)心到底使用哪些寄存器,完全依靠GCC來安排和指定;這一點可以讓程序員免去考慮有限的寄存器的使用,也可以提高目標代碼的效率; 1.帶有C/C++表達式的內(nèi)聯(lián)匯編語句的格式: __asm__ [__volatile__]("instruction list":Output:Input:Clobber/Modify); 圓括號中的內(nèi)容被冒號":"分為四個部分: A.如果第四部分的"Clobber/Modify"可以為空;如果"Clobber/Modify"為空,則其前面的冒號(:)必須省略;比如:語句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp):);是非法的,而語句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp));則是合法的; B.如果第一部分的"instruction list"為空,則input、output、Clobber/Modify可以為空,也可以不為空;比如,語句__asm__("":::"memory");和語句__asm__(""::);都是合法的寫法; C.如果Output、Input和Clobber/Modify都為空,那么,Output、Input之前的冒號(:)可以省略,也可以不省略;如果都省略,則此匯編就退化為一個基本匯編,否則,仍然是一個帶有C/C++表達式的內(nèi)聯(lián)匯編,此時"instruction list"中的寄存器的寫法要遵循相關(guān)規(guī)定,比如:寄存器名稱前面必須使用兩個百分號(%%);基本內(nèi)聯(lián)匯編中的寄存器名稱前面只有一個百分號(%);比如,語句__asm__("movl %%eax,%%ebx"::);__asm__("movl %%eax,%%ebx":);和語句__asm__("movl %%eax,%%ebx");都是正確的寫法,而語句__asm__("movl %eax,%ebx"::);__asm__("movl %eax,%ebx":);和語句__asm__("movl %%eax,%%ebx");都是錯誤的寫法; D.如果Input、Clobber/Modify為空,但Output不為空,則,Input前面的冒號(:)可以省略,也可以不省略;比如,語句__asm__("movl %%eax,%%ebx":"=b"(foo):);和語句__asm__("movl %%eax,%%ebx":"=b"(foo));都是正確的; E.如果后面的部分不為空,而前面的部分為空,則,前面的冒號(:)都必須保留,否則無法說明不為空的部分究竟是第幾部分;比如,Clobber/Modify、Output為空,而Input不為空,則Clobber/Modify前面的冒號必須省略,而Output前面的冒號必須保留;如果Clobber/Modify不為空,而Input和Output都為空,則Input和Output前面的冒號都必須保留;比如,語句__asm__("movl %%eax,%%ebx"::"a"(foo));和__asm__("movl %%eax,%%ebx":::"ebx"); 注意:基本內(nèi)聯(lián)匯編中的寄存器名稱前面只能有一個百分號(%),而帶有C/C++表達式的內(nèi)聯(lián)匯編中的寄存器名臣前面必須有兩個百分號(%%); 2.Output: Output部分用來指定當前內(nèi)聯(lián)匯編語句的輸出,稱為輸出表達式; 格式為: "操作約束"(輸出表達式) 例如: __asm__("movl %%rc0,%1":"=a"(cr0)); 這個語句中的Output部分就是("=a"(cr0)),它是一個操作表達式,指定了一個內(nèi)聯(lián)匯編語句的輸出部分; Output部分由兩個部分組成:由雙引號括起來的部分和由圓括號括起來的部分,這兩個部分是一個Output部分所不可缺少的部分; 用雙引號括起來的部分就是C/C++表達式,它用于保存當前內(nèi)聯(lián)匯編語句的一個輸出值,其操作就是C/C++賦值語句"="的左值部分,因此,圓括號中指定的表達式只能是C/C++中賦值語句的左值表達式,即:放在等號=左邊的表達式;也就是說,Output部分只能作為C/C++賦值操作左邊的表達式使用; 用雙引號括起來的部分就指定了C/C++中賦值表達式的右值來源;這個部分被稱作是"操作約束"(Operation Constraint),也可以稱為"輸出約束";在這個例子中的操作約束是"=a",這個操作約束包含兩個組成部分:等號(=)和字母a,其中,等號(=)說明圓括號中的表達式cr0是一個只寫的表達式,只能被用作當前內(nèi)聯(lián)匯編語句的輸出,而不能作為輸入;字母a是寄存器EAX/AX/AL的縮寫,說明cr0的值要從寄存器EAX中獲取,也就是說cr0=eax,最終這一點被轉(zhuǎn)化成匯編指令就是:movl %eax,address_of_cr0; 注意:很多文檔中都聲明,所有輸出操作的的操作約束都必須包含一個等號(=),但是GCC的文檔中卻明確地聲明,并非如此;因為等號(=)約束說明當前的表達式是一個只寫的,但是還有另外一個符號:加號(+),也可以用來說明當前表達式是可讀可寫的;如果一個操作約束中沒有給出這兩個符號中的任何一個,則說明當前表達式是只讀的;因此,對于輸出操作來說,肯定必須是可寫的,而等號(=)和加號(+)都可表示可寫,只不過加號(+)同時也可以表示可讀;所以,對于一個輸出操作來說,其操作約束中只要包含等號(=)或加號(+)中的任意一個就可以了; 等號(=)與加號(+)的區(qū)別:等號(=)表示當前表達式是一個純粹的輸出操作,而加號(+)則表示當前表達式不僅僅是一個輸出操作,還是一個輸入操作;但無論是等號(=)還是加號(+),所表示的都是可寫,只能用于輸出,只能出現(xiàn)在Output部分,而不能出現(xiàn)在Input部分; 在Output部分可以出現(xiàn)多個輸出操作表達式,多個輸出操作表達式之間必須用逗號(,)隔開; 3、Input: Input部分用來指定當前內(nèi)聯(lián)匯編語句的輸入;稱為輸入表達式; 格式為: "操作約束"(輸入表達式) 例如: __asm__("movl %0,%%db7"::"a"(cpu->db7)); 其中,表達式"a"(cpu->db7)就稱為輸入表達式,用于表示一個對當前內(nèi)聯(lián)匯編的輸入; Input同樣也由兩部分組成:由雙引號括起來的部分和由圓括號括起來的部分;這兩個部分對于當前內(nèi)聯(lián)匯編語句的輸入來說也是必不可少的; 在這個例子中,由雙引號括起來的部分是"a",用圓括號括起來的部分是(cpu->db7); 用雙引號括起來的部分就是C/C++表達式,它為當前內(nèi)聯(lián)匯編語句提供一個輸入值;在這里,圓括號中的表達式cpu->db7是一個C/C++語言的表達式,它不必是左值表達式,也就是說,它不僅可以是放在C/C++賦值操作左邊的表達式,還可以是放在C/C++賦值操作右邊的表達式;所以,Input可以是一個變量、一個數(shù)字,還可以是一個復雜的表達式(如:a+b/c*d); 比如,上例還可以這樣寫: __asm__("movl %0,%%db7"::"a"(foo));__asm__("movl %0,%%db7"::"a"(0x12345));__asm__("movl %0,%%db7"::"a"(va:vb/vc)); 用雙引號括起來的部分就是C/C++中賦值表達式的右值表達式,用于約束當前內(nèi)聯(lián)匯編語句中的當前輸入;這個部分也成為"操作約束",也可以成為是"輸入約束";與輸出表達式中的操作約束不同的是,輸入表達式中的操作約束不允許指定等號(=)約束或加號(+)約束,也就是說,它只能是只讀的;約束中必須指定一個寄存器約束;例子中的字母a表示當前輸入變量cpu->db7要通過寄存器EAX輸入到當前內(nèi)聯(lián)匯編語句中; 三、操作約束:Operation Constraint 操作約束只會出現(xiàn)在帶有C/C++表達式的內(nèi)聯(lián)匯編語句中; 每一個Input和Output表達式都必須指定自己的操作約束Operation Constraint;約束的類型有:寄存器約束、內(nèi)存約束、立即數(shù)約束、通用約束; 操作表達式的格式: "約束"(C/C++表達式) 即:"Constraint"(C/C++ expression) 1.寄存器約束: 當你的輸入或輸出需要借助于一個寄存器時,你需要為其指定一個寄存器約束; 可以直接指定一個寄存器名字;比如: __asm__ __volatile__("movl %0,%%cr0"::"eax"(cr0)); 也可以指定寄存器的縮寫名稱;比如: __asm__ __volatile__("movl %0,%%cr0"::"a"(cr0)); 如果指定的是寄存器的縮寫名稱,比如:字母a;那么,GCC將會根據(jù)當前操作表達式中C/C++表達式的寬度來決定使用%eax、%ax還是%al;比如: unsigned short __shrt; __asm__ __volatile__("movl %0,%%bx"::"a"(__shrt)); 由于變量__shrt是16位無符號類型m大小是兩個字節(jié),所以,編譯器編譯出來的匯編代碼中,則會讓此變量使用寄存器%ax; 無論是Input還是Output操作約束,都可以使用寄存器約束; 常用的寄存器約束的縮寫: r:I/O,表示使用一個通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中選取一個GCC認為是合適的; q:I/O,表示使用一個通用寄存器,與r的意義相同; g:I/O,表示使用寄存器或內(nèi)存地址; m:I/O,表示使用內(nèi)存地址; a:I/O,表示使用%eax/%ax/%al; b:I/O,表示使用%ebx/%bx/%bl; c:I/O,表示使用%ecx/%cx/%cl; d:I/O,表示使用%edx/%dx/%dl; D:I/O,表示使用%edi/%di; S:I/O,表示使用%esi/%si; f:I/O,表示使用浮點寄存器; t:I/O,表示使用第一個浮點寄存器; u:I/O,表示使用第二個浮點寄存器; A:I/O,表示把%eax與%edx組合成一個64位的整數(shù)值; o:I/O,表示使用一個內(nèi)存位置的偏移量; V:I/O,表示僅僅使用一個直接內(nèi)存位置; i:I/O,表示使用一個整數(shù)類型的立即數(shù); n:I/O,表示使用一個帶有已知整數(shù)值的立即數(shù); F:I/O,表示使用一個浮點類型的立即數(shù); 2.內(nèi)存約束: 如果一個Input/Output操作表達式的C/C++表達式表現(xiàn)為一個內(nèi)存地址(指針變量),不想借助于任何寄存器,則可以使用內(nèi)存約束;比如: __asm__("lidt %0":"=m"(__idt_addr));或__asm__("lidt %0"::"m"(__idt_addr)); 內(nèi)存約束使用約束名"m",表示的是使用系統(tǒng)支持的任何一種內(nèi)存方式,不需要借助于寄存器; 使用內(nèi)存約束方式進行輸入輸出時,由于不借助于寄存器,所以,GCC不會按照你的聲明對其做任何的輸入輸出處理;GCC只會直接拿來使用,對這個C/C++表達式而言,究竟是輸入還是輸出,完全依賴于你寫在"instruction list"中的指令對其操作的方式;所以,不管你把操作約束和操作表達式放在Input部分還是放在Output部分,GCC編譯生成的匯編代碼都是一樣的,程序的執(zhí)行結(jié)果也都是正確的;本來我們將一個操作表達式放在Input或Output部分是希望GCC能為我們自動通過寄存器將表達式的值輸入或輸出;既然對于內(nèi)存約束類型的操作表達式來說,GCC不會為它做任何事情,那么放在哪里就無所謂了;但是從程序員的角度來看,為了增強代碼的可讀性,最好能夠把它放在符合實際情況的地方; 3.立即數(shù)約束: 如果一個Input/Output操作表達式的C/C++表達式是一個數(shù)字常數(shù),不想借助于任何寄存器或內(nèi)存,則可以使用立即數(shù)約束; 由于立即數(shù)在C/C++表達式中只能作為右值使用,所以,對于使用立即數(shù)約束的表達式而言,只能放在Input部分;比如: __asm__ __volatile__("movl %0,%%eax"::"i"(100)); 立即數(shù)約束使用約束名"i"表示輸入表達式是一個整數(shù)類型的立即數(shù),不需要借助于任何寄存器,只能用于Input部分;使用約束名"F"表示輸入表達式是一個浮點數(shù)類型的立即數(shù),不需要借助于任何寄存器,只能用于Input部分; 4.通用約束: 約束名"g"可以用于輸入和輸出,表示可以使用通用寄存器、內(nèi)存、立即數(shù)等任何一種處理方式; 約束名"0,1,2,3,4,5,6,7,8,9"只能用于輸入,表示與第n個操作表達式使用相同的寄存器/內(nèi)存; 通用約束"g"是一個非常靈活的約束,當程序員認為一個C/C++表達式在實際操作中,無論使用寄存器方式、內(nèi)存方式還是立即數(shù)方式都無所謂時,或者程序員想實現(xiàn)一個靈活的模板,以讓GCC可以根據(jù)不同的C/C++表達式生成不同的訪問方式時,就可以使用通用約束g; 例如: #define JUST_MOV(foo) __asm__("movl %0,%%eax"::"g"(foo)) 則,JUST_MOV(100)和JUST_MOV(var)就會讓編譯器產(chǎn)生不同的匯編代碼; 對于JUST_MOV(100)的匯編代碼為: #APP movl $100,%eax #立即數(shù)方式; #NO_APP 對于JUST_MOV(var)的匯編代碼為: #APP movl 8(%ebp),%eax #內(nèi)存方式; #NO_APP 像這樣的效果,就是通用約束g的作用; 5.修飾符: 等號(=)和加號(+)作為修飾符,只能用于Output部分;等號(=)表示當前輸出表達式的屬性為只寫,加號(+)表示當前輸出表達式的屬性為可讀可寫;這兩個修飾符用于約束對輸出表達式的操作,它們倆被寫在輸出表達式的約束部分中,并且只能寫在第一個字符的位置; 符號&也寫在輸出表達式的約束部分,用于約束寄存器的分配,但是只能寫在約束部分的第二個字符的位置上;
用符號&進行修飾時,等于向GCC聲明:"GCC不得為任何Input操作表達式分配與此Output操作表達式相同的寄存器"; 其原因是修飾符&意味著被其修飾的Output操作表達式要在所有的Input操作表達式被輸入之前輸出; 即:GCC會先使用輸出值對被修飾符&修飾的Output操作表達式進行填充,然后,才對Input操作表達式進行輸入; 這樣的話,如果不使用修飾符&對Output操作表達式進行修飾,一旦后面的Input操作表達式使用了與Output操作表達式相同的寄存器,就會產(chǎn)生輸入輸出數(shù)據(jù)混亂的情況;相反,如果沒有用修飾符&修飾輸出操作表達式,那么,就意味著GCC會先把Input操作表達式的值輸入到選定的寄存器中,然后經(jīng)過處理,最后才用輸出值填充對應的Output操作表達式; 所以,修飾符&的作用就是要求GCC編譯器為所有的Input操作表達式分配別的寄存器,而不會分配與被修飾符&修飾的Output操作表達式相同的寄存器;修飾符&也寫在操作約束中,即:&約束;由于GCC已經(jīng)規(guī)定加號(+)或等號(=)占據(jù)約束的第一個字符,那么&約束只能占用第二個字符;
例如: int __out, __in1, __in2; __asm__("popl %0\n\t" "movl %1,%%esi\n\t" "movl %2,%%edi\n\t" :"=&a"(__out) :"r"(__in1),"r"(__in2));
注意:如果一個Output操作表達式的寄存器約束被指定為某個寄存器,只有當至少存在一個Input操作表達式的寄存器約束為可選約束(意思是GCC可以從多個寄存器中選取一個,或使用非寄存器方式)時,比如"r"或"g"時,此Output操作表達式使用符號&修飾才有意義;如果你為所有的Input操作表達式指定了固定的寄存器,或使用內(nèi)存/立即數(shù)約束時,則此Output操作表達式使用符號&修飾沒有任何意義; 比如: __asm__("popl %0\n\t" "movl %1,%esi\n\t" "movl %2,%edi\n\t" :"=&a"(__out) :"m"(__in1),"c"(__in2)); 此例中的Output操作表達式完全沒有必要使用符號&來修飾,因為__in1和__in2都已經(jīng)被指定了固定的寄存器,或使用了內(nèi)存方式,GCC無從選擇; 如果你已經(jīng)為某個Output操作表達式指定了修飾符&,并指定了固定的寄存器,那么,就不能再為任何Input操作表達式指定這個寄存器了,否則會出現(xiàn)編譯報錯; 比如: __asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&a"(__out):"a"(__in1),"c"(__in2)); 對這條語句的編譯就會報錯; 相反,你也可以為Output指定可選約束,比如"r"或"g"等,讓GCC為此Output操作表達式選擇合適的寄存器,或使用內(nèi)存方式,GCC在選擇的時候,會排除掉已經(jīng)被Input操作表達式所使用過的所有寄存器,然后在剩下的寄存器中選擇,或者干脆使用內(nèi)存方式; 比如: __asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&r"(__out):"a"(__in1),"c"(__in2)); 這三個修飾符只能用在Output操作表達式中,而修飾符%則恰恰相反,它只能用在Input操作表達式中; 修飾符%用于向GCC聲明:"當前Input操作表達式中的C/C++表達式可以與下一個Input操作表達式中的C/C++表達式互換";這個修飾符一般用于符合交換律運算的地方;比如:加、乘、按位與&、按位或|等等; 例如: __asm__("addl %1,%0\n\t":"=r"(__out):"%r"(__in1),"0"(__in2)); 其中,"0"(__in2)表示使用與第一個Input操作表達式("r"(__in1))相同的寄存器或內(nèi)存; 由于使用符號%修飾__in1的寄存器方式r,那么就表示,__in1與__in2可以互換位置;加法的兩個操作數(shù)交換位置之后,和不變; 修飾符 I/O 意義 = O 表示此Output操作表達式是只寫的 + O 表示此Output操作表達式是可讀可寫的 & O 表示此Output操作表達式獨占為其指定的寄存器 % I 表示此Input操作表達式中的C/C++表達式可以與下一個Input操作表達式中的C/C++表達式互換 四、占位符 每一個占位符對應一個Input/Output操作表達式; 帶C/C++表達式的內(nèi)聯(lián)匯編中有兩種占位符:序號占位符和名稱占位符; 1.序號占位符: GCC規(guī)定:一個內(nèi)聯(lián)匯編語句中最多只能有10個Input/Output操作表達式,這些操作表達式按照他們被列出來的順序依次賦予編號0到9;對于占位符中的數(shù)字而言,與這些編號是對應的;比如:占位符%0對應編號為0的操作表達式,占位符%1對應編號為1的操作表達式,依次類推; 由于占位符前面要有一個百分號%,為了去邊占位符與寄存器,GCC規(guī)定:在帶有C/C++表達式的內(nèi)聯(lián)匯編語句的指令列表里列出的寄存器名稱前面必須使用兩個百分號(%%),一區(qū)別于占位符語法; GCC對占位符進行編譯的時候,會將每一個占位符替換為對應的Input/Output操作表達式所指定的寄存器/內(nèi)存/立即數(shù); 例如: __asm__("addl %1,%0\n\t":"=a"(__out):"m"(__in1),"a"(__in2)); 這個語句中,%0對應Output操作表達式"=a"(__out),而"=a"(__out)指定的寄存器是%eax,所以,占位符%0被替換為%eax;占位符%1對應Input操作表達式"m"(__in1),而"m"(__in1)被指定為內(nèi)存,所以,占位符%1被替換位__in1的內(nèi)存地址; 用一句話描述:序號占位符就是前面描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9;其中,每一個占位符對應一個Input/Output的C/C++表達式; 2.名稱占位符: 由于GCC中限制這種占位符的個數(shù)最多只能由這10個,這也就限制了Input/Output操作表達式中C/C++表達式的數(shù)量做多只能有10個;如果需要的C/C++表達式的數(shù)量超過10個,那么,這些需要占位符就不夠用了;
GCC內(nèi)聯(lián)匯編提供了名稱占位符來解決這個問題;即:使用一個名字字符串與一個C/C++表達式對應;這個名字字符串就稱為名稱占位符;而這個名字通常使用與C/C++表達式中的變量完全相同的名字; 使用名字占位符時,內(nèi)聯(lián)匯編的Input/Output操作表達式中的C/C++表達式的格式如下: [name] "constraint"(變量) 此時,指令列表中的占位符的書寫格式如下: %[name] 這個格式等價于序號占位符中的%0,%1,$2等等; 使用名稱占位符時,一個name對應一個變量; 例如: __asm__("imull %[value1],%[value2]" :[value2] "=r"(data2) :[value1] "r"(data1),"0"(data2)); 此例中,名稱占位符value1就對應變量data1,名稱占位符value2對應變量data2;GCC編譯的時候,同樣會把這兩個占位符分別替換成對應的變量所使用的寄存器/內(nèi)存地址/立即數(shù);而且也增強了代碼的可讀性; 這個例子,使用序號占位符的寫法如下: __asm__("imull %1,%0" :"=r"(data2) :"r"(data1),"0"(data2)); 五、寄存器/內(nèi)存修改標示(Clobber/Modify) 有時候,當你想通知GCC當前內(nèi)聯(lián)匯編語句可能會對某些寄存器或內(nèi)存進行修改,希望GCC在編譯時能夠?qū)⑦@一點考慮進去;那么你就可以在Clobber/Modify部分聲明這些寄存器或內(nèi)存; 1.寄存器修改通知: 這種情況一般發(fā)生在一個寄存器出現(xiàn)在指令列表中,但又不是Input/Output操作表達式所指定的,也不是在一些Input/Output操作表達式中使用"r"或"g"約束時由GCC選擇的,同時,此寄存器被指令列表中的指令所修改,而這個寄存器只供當前內(nèi)聯(lián)匯編語句使用的情況;比如: __asm__("movl %0,%%ebx"::"a"(__foo):"bx"); //這個內(nèi)聯(lián)匯編語句中,%ebx出現(xiàn)在指令列表中,并且被指令修改了,但是卻未被任何Input/Output操作表達式是所指定,所以,你需要在Clobber/Modify部分指定"bx",以讓GCC知道這一點; 因為你在Input/Output操作表達式中指定的寄存器,或當你為一些Input/Output操作表達式使用"r"/"g"約束,讓GCC為你選擇一個寄存器時,GCC對這些寄存器的狀態(tài)是非常清楚的,它知道這些寄存器是被修改的,你根本不需要在Clobber/Modify部分聲明它們;但除此之外,GCC對剩下的寄存器中哪些會被當前內(nèi)聯(lián)匯編語句所修改則一無所知;所以,如果你真的在當前內(nèi)聯(lián)匯編指令中修改了它們,那么就最好在Clobber/Modify部分聲明它們,讓GCC針對這些寄存器做相應的處理;否則,有可能會造成寄存器不一致,從而造成程序執(zhí)行錯誤;
在Clobber/Modify部分聲明這些寄存器的方法很簡單,只需要將寄存器的名字用雙引號括起來就可以;如果要聲明多個寄存器,則相鄰兩個寄存器名字之間用逗號隔開; 例如: __asm__("movl %0,%%ebx; popl %%ecx"::"a"(__foo):"bx","cx"); 這個語句中,聲明了bx和cx,告訴GCC:寄存器%ebx和%ecx可能會被修改,要求GCC考慮這個因素; 寄存器名稱串: "al"/"ax"/"eax":代表寄存器%eax "bl"/"bx"/"ebx":代表寄存器%ebx "cl"/"cx"/"ecx":代表寄存器%ecx "dl"/"dx"/"edx":代表寄存器%edx "si"/"esi":代表寄存器%esi "di"/"edi":代表寄存器%edi 所以,只需要使用"ax","bx","cx","dx","si","di"就可以了,因為他們都代表對應的寄存器; 如果你在一個內(nèi)斂匯編語句的Clobber/Modify部分向GCC聲明了某個寄存器內(nèi)存發(fā)生了改變,GCC在編譯時,如果發(fā)現(xiàn)這個被聲明的寄存器的內(nèi)容在此內(nèi)聯(lián)匯編之后還要繼續(xù)使用,那么,GCC會首先將此寄存器的內(nèi)容保存起來,然后在此內(nèi)聯(lián)匯編語句的相關(guān)代碼生成之后,再將其內(nèi)容回復; 另外需要注意的是,如果你在Clobber/Modify部分聲明了一個寄存器,那么這個寄存器將不能再被用作當前內(nèi)斂匯編語句的Input/Output操作表達式的寄存器約束,如果Input/Output操作表達式的寄存器約束被指定為"r"/"g",GCC也不會選擇已經(jīng)被聲明在Clobber/Modify部分中的寄存器; 例如: __asm__("movl %0,%%ebx"::"a"(__foo):"ax","bx"); 這條語句中的Input操作表達式"a"(__foo)中已經(jīng)指定了寄存器%eax,那么在Clobber/Modify部分中個列出的"ax"就是非法的;編譯時,GCC會報錯; 2.內(nèi)存修改通知: 除了寄存器的內(nèi)容會被修改之外,內(nèi)存的內(nèi)容也會被修改;如果一個內(nèi)聯(lián)匯編語句的指令列表中的指令對內(nèi)存進行了修改,或者在此內(nèi)聯(lián)匯編出現(xiàn)的地方,內(nèi)存內(nèi)容可能發(fā)生改變,而被改變的內(nèi)存地址你沒有在其Output操作表達式中使用"m"約束,這種情況下,你需要使用在Clobber/Modify部分使用字符串"memory"向GCC聲明:"在這里,內(nèi)存發(fā)生了,或可能發(fā)生了改變"; 例如: void* memset(void* s, char c, size_t count) { __asm__("cld\n\d" "rep\n\t" "stosb" :/*no output*/ :"a"(c),"D"(s),"c"(count) :"cx","di","memory"); return s; } 如果一個內(nèi)聯(lián)匯編語句的Clobber/Modify部分存在"memory",那么GCC會保證在此內(nèi)聯(lián)匯編之前,如果某個內(nèi)存的內(nèi)容被裝入了寄存器,那么,在這個內(nèi)聯(lián)匯編之后,如果需要使用這個內(nèi)存處的內(nèi)容,就會直接到這個內(nèi)存處重新讀取,而不是使用被存放在寄存器中的拷貝;因為這個時候寄存器中的拷貝很可能已經(jīng)和內(nèi)存處的內(nèi)容不一致了; 3.標志寄存器修改通知: 當一個內(nèi)聯(lián)匯編中包含影響標志寄存器eflags的條件,那么也需要在Clobber/Modify部分中使用"cc"來向GCC聲明這一點;
|