GCC內聯彙編基礎

這篇文章闡述內聯彙編的使用方法。顯然,閱讀這篇文章您需要具備X86彙編語言和C語言的基礎知識。
Contents 1. 簡介 3 2. 概要 3 3. GCC彙編格式。 3 1) 源操作數和目的操作數的方向 3 2) 寄存器命名 4 3) 立即數 4 4) 操作數大小 4 5) 內存操作數 4 4. 基本形式的內聯彙編 4 5. 擴展形式的內聯彙編 5 5.1 彙編模板 6 5.2 操作數 6 5.3 Clobber List 7 5.4 Volatile…? 8 6. 深入constraints 8 6.1 常用constraints 8 6.2 constraint修改標記 10 7.常用技巧 10 8.結束語 13 9. 參考文獻 13
1. 簡介
[主要是版權/反饋/勘誤/感謝等信息。沒有翻譯。--譯者注, 本文中方括號中的都是譯者注]
2. 概要
我們現在學習GCC內聯彙編,那麼內聯彙編到底是什麼?
[我們首先先來看看內聯函數有什麼好處]
我們可以讓編譯器將函數代碼插入到調用者代碼中,指出函數在代碼中具體什麼位置被執行。這種函數就是內聯函數。內聯函數似乎很像一個宏?的確,他們之間有很多相似之處。
那麼內聯函數到底有什麼好處呢?
內聯函數降低了函數調用的開銷。[不僅僅節省堆棧]
如果某些函數調用的實參相同,那麼返回值一定是相同的,這就可能給編譯器留下了簡化的空間。因爲返回值相同了就不必把內聯函數的代碼插入到調用者的代碼中
[直接用這個返回值替換就好了]。這樣可以減少代碼量,視不同的情況而定。聲明一個函數是內聯函數,使用關鍵字 inline。
現在我們回到內聯彙編上來。內聯彙編就是一些彙編語句寫成的內聯函數。它方便,快速,對系統編程非常有用。我們主要目標是研究GCC內聯函數的基礎格式和使用方法。聲明一個內聯彙編函數,我們使用關鍵字 asm。
內聯彙編的重要性首先體現在它的操作C語言變量和輸出值到C語言變量的能力。由於這些特性,內聯彙編常被用作彙編指令和調用它的C程序之間的接口。
3. GCC彙編格式
GCC
(GNU Compiler for Linux) 使用AT&T
UNIX彙編語法.這裏我們將用AT&T彙編格式來寫代碼。如果你不熟悉AT&T彙編語法也沒有關係,下面將有介紹。AT&T和
Intel彙編語法有很多的不同之處。我將給出主要的不同點。
1) 源操作數和目的操作數的方向
AT&T和Intel彙編語法相反。Intel語法中第一個操作數作爲目的操作數,第二個操作數作爲源操作數。相反,在AT&T語法中,第一個操作數是源操作數,第二個是目的操作數,例如:
Intel語法: "OP-code dst src"
AT&T語法: "Op-code src dst"
2) 寄存器命名
[在AT&T語法中] 寄存器名字加上%前綴,例如,如果要使用eax, 寫作: %eax.
3) 立即數
AT&T
語法中,立即數以'$'符號作爲前綴。靜態C變量前也要加上'$'前綴。在Intel語法中,16進制的常數加上'h'後綴,但是在AT&T中,
常量前要加上'0x'。 對於一個16進制常數(在AT&T中),首先以$開頭接着是0x,最後是常數。
4) 操作數大小

AT&T語法中,操作數佔內存大小決定於彙編命令操作符的最後一個字符的內容。 操作符以'b', 'w'和 'l'爲後綴指明內存訪問長度是
byte(8-bit), word(16-bit)還是long(32-bit). 而Intel語法在操作數前加上'byte ptr',
'word ptr'和'dword ptr'的內存操作數(這個操作數不是彙編命令操作符)來達到相同目的.
因此, Intel "mov al, byte ptr foo" 用AT&T語法就是 :"movb foo, %al"
5) 內存操作數
在Intel的語法中,基址寄存器用'['和']'擴起來,但是在AT&T中改用'('和')'。 此外,在Intel語法中一個間接內存尋址:
section:[base + index * scale + disp],在AT&T中則爲:
section:disp(base, index, scale)
總是需要記住的一點就是,當一個常數被用作disp或者scale時,就不用加'$'前綴。
現在我們已經提到了AT&T和Intel語法的一些主要不同點。 我只是提到了一小部分。全部內容可以參考GNU彙編文檔。爲了更好理解這些不同,請看下面的例子:
Intel Code                              AT&T Code
mov eax,1                               movl $1, %eax
mov ebx,0ffh                            movl $0x0ff,%ebx,
int 80h                                 int $0x80
mov ebx,eax                             movl %eax,%ebx
mov eax,[ecx]                           movl (%ecx),%eax
mov eax,[ecx+3]                         movl 3(ecx),eax
mov eax,[ebx+20h]                       movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h]                    addl (%ebx,%ecx,%0x2),%eax
lea eax,[ebx+ecx]                       leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]                subl -0x20(%ebx,%ecx,0x4),%eax
4. 基本形式的內聯彙編
基本形式的內聯彙編格式非常直觀:
asm(“assembly code”);
例如:
asm("movl %ecx, %eax"); /* 把 ecx 內容移動到 eax */
__asm__("movb %bh , (%eax)"); /* 把bh中一個字節的內容移動到eax 指向的內存 */
你可能注意到了這裏使用了 asm 和
__asm__關鍵字.二者皆可。這樣如果asm關鍵字和程序其他變量有衝突就可以使用__asm__了。如果有超過一行的的指令,每行要加上雙引號,並且後面加上\n\t. 這是因爲GCC將每行指令作爲一個字符串傳給as(GAS),使用換行和TAB可以給彙編器傳送正確的格式化好的代碼行。
例如:
__asm__ ("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");
如果我們的代碼涉及到一些寄存器(例如改變了其內容)並且從彙編代碼返回後並沒有修復這些改變,一些意想不到的情況可能發生。因爲GCC不知道你已經將寄存器內容改了,這將給我們帶來麻煩,尤其在編譯器作了一些優化的情況下。如果不告訴GCC,編譯器將認爲寄存器中事實上已經被改掉了的內容沒有被改過,程序將當作它沒有被改過而繼續執行。
我們能做的就是不要使用這些帶來其他附加影響的語句或者當我們退出的時候還原這些內容,否則只有等待程序崩潰了。這裏提到的這種情況就是我們將要在下節中
闡述的擴展形式的內聯彙編。
5. 擴展形式的內聯彙編
前面介紹的基礎形式的內聯彙編方法只涉及到嵌入彙編指令。在高級形式中,我們將可以指定操作數,它允許我們指
定輸入輸出寄存器[內聯函數使用這些寄存器作爲存儲輸入輸出變量]和程序中涉及到的clobbered寄存器列表[clobbered
registers:內聯彙編程序可能要改變其內容的寄存器]。也並不是一定要要顯式指明使用具體的寄存器,我們也可以把它留給GCC去選擇,這樣GCC
還可能更好的進行優化處理。高級內聯彙編的基本格式如下:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);

中assembler
template包含彙編指令部分。括號中每個操作數用C表達式常量串描述。不同部分之間用冒號分開。相同部分中的每個小部分用逗號分開。操作數多少被限
定爲10或者由機器決定的一個最大值[這句話翻譯的不好,貼出原文: The total number of operands is
limited to ten or to the maximum number of operands in any instruction
pattern in the machine description, whichever is
greater.]。
如果沒有輸出部分但是有輸入部分,就必須在輸出部分之前連續寫兩個冒號。
例如 :
asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* no output registers */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);
現在我們來分析上面的代碼的功能。上面代碼循環count次把fill_value的值到填充到edi寄存器指定的內存位置。並且告訴GCC寄存器eax[這裏應該是ecx]和edi中內容可能已經被改變了。
爲了有一個更清晰的理解,我們再來看一個例子:
int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
上面代碼所做的就是用匯編代碼把a的值賦給b。值得注意的幾點有:
1) “b”是輸出操作數,用%0來訪問,”a”是輸入操作數,用%1來訪問。
2)
“r” 是一個constraint, 關於constraint後面有詳細的介紹。這裏我們只要記住這裏constraint
”r”讓GCC自己選擇一個寄存器去存儲變量a。輸出部分的constraint前必須要有個 ”=”,用來說明是一個這是一個輸出操作數,並且只寫。
3) 你可能看到有的寄存器名字前面寫了兩個%,這是用來幫助GCC區分操作數和寄存器。操作數只需要一個%前綴。
4) 在第三個冒號後面的clobbered register部分, %eax 說明在內聯彙編代碼中將要改變eax中的內容,GCC不要用他存儲其他值。
當這段代碼執行結束後,”b”的值將會被改掉,因爲它被指定作爲輸出操作數。換句話說,在”asm”內部對b的改動將影響到asm外面.
下面我們將對各個部分分別進行詳細的討論:
5.1 彙編模板
彙編模板部分包含嵌入到C程序中的彙編指令。格式如下:
每條指令放在一個雙引號內,或者將所有的指令都放着一個雙引號內。每條指令都要包含一個分隔符。合法的分隔符是換行符(\n)或者分號。用換行符的時候通常
後面放一個製表符”\t”。我們已經知道爲什麼使用換行符+製表符了[前面部分有解釋]。其中,訪問C操作數用%0,%1…等等。
5.2 操作數
C語言表達式 [大多情況是C變量] 將作爲”asm”內部使用的操作數。每一個操作數都以雙引號開始。對於輸出操作數,還要寫一個修改標誌(=)。constraint和修改標誌都放在雙引號內。接下來部分就是C表達式了[放在括號內].舉例來說:
標準形式如下:
"constraint" (C expression) [ 如: “=r”(result) ]
對於輸出操作數還有一個修改標誌(=)。 constraint主要用來指定操作數的尋址類型 (內存尋址或寄存器尋址),也用來指明使用哪個寄存器。
如果有多個操作數,之間用逗號分隔。
在彙編模板中,每個操作數都用數字引用[這些操作數],引用規則如下,如果總共有n個操作數(包括輸入輸出操作數),那麼第一個輸出操作引用數字爲0,依次遞增,然後最後一個操作數是n-1。關於最多操作數限制參見前面的小結。
輸出操作數表達式必須是左值,輸入操作數沒有這個限制。注意這裏可以使表達式[不僅僅限於變量]。高級彙編形式常用在當編譯器不知道這個機器指令存在的時候。;-)如果輸出表達式不能直接尋址(比如是bit-field),
constraint就必須指定一個寄存器.這種情況下,GCC將使用寄存器作爲asm的輸出。然後保存這個寄存器的值到輸出表達式中。
如上所述,一般輸出操作數必須是隻寫的;GCC將認爲在這條指令之前,保存在這種操作數中的值已經過期和不再需要了 。高級形式的asm也支持輸入輸出或者讀寫操作數。
現在我們來看一些例子,把一個數字乘以5使用匯編指令lea
asm( “leal (%1,%1,4), %0”
: ”=r” (five_times_x)
: “r” (x)
);
這裏輸入操作數是 ‘x’,不指定具體使用那個寄存器,GCC會自己選擇輸入輸出的寄存器來操作。如果我們也可以讓GCC把輸入和輸出寄存器限定同一個。只需要使用讀寫操作數,使用合適的constraint,看下具體方法:
asm(“lea (%0,%0,4),%0”
: “=r” (five_times_x)
: “0” (x)
);
上面使輸入和輸出操作數存在相同的寄存器中,我們不知道GCC具體使那個寄存器,但是我們也可以指定一個,像這樣:
asm(“lea (%0,%0,4),%0”
: “=c” (five_times_x)
: “c” (x)
);
上面的三個例子中,我沒有都沒有在clobber
list中放入任何寄存器的值,這是爲什麼?在前兩個例子中,GCC決定使用那個寄存器並且自己知道哪兒改變了。第三個例子中我們也沒有必要把ecx放在
clobber list中是因爲GCC知道X將存入其中。因爲GCC知道ecx的值,所以我們也不用放入clobber list.
5.3 Clobber List
一些指令破壞了一個寄存器值,我們就不得不在asm裏面第三個冒號後的Clobber
List中標示出來,通知GCC這個裏面的值要被改掉。這樣GCC將不再假設之前存入這些寄存器中的值是合法的了。我們不需要把輸入輸出寄存器在這個部分
標出,因爲GCC知道asm將使用這些寄存器。(因爲它們已經顯式的被作爲輸入輸出標出)
。如果此外指令中還用到其他寄存器無論顯示還是隱式的使用到(沒有在輸入輸出中標示出的),這些指令必須在clobbered list中標明。
如果指令中以不可預見形式修改了內存值,要加上”memory”到clobbered list中。這使得GCC不去緩存在這些內存值。還有,如果內存被改變而沒有被列在輸入和出部分 要加上volatile關鍵字。
如果需要可以對clobbered 寄存器多次讀寫。來看一個乘法的例子;調用函數 _foo要求接受在eax和ecx值作爲參數。
asm(“movl %0,%%eax;
“movl %1,%%ecx;
Call _foo”
:/*no outputs*/
:”g” (from), ”g” (to)
: “eax”, “ecx”
);
5.4 Volatile…?
如果你熟悉內核代碼或者一些類似優秀的代碼,你一定見過很多在asm或者__asm__後的函數聲明前加了volatile 或者__volatile__。之前我提到過關於asm和__asm__,但是volatile有什麼用途呢?
如果彙編代碼必須在我們放的位置被執行(例如不能被循環優化而移出循環),那就在asm之後()之前,放一個valatile關鍵字。 這樣可以禁止這些代碼被移動或刪除,我們可以這樣聲明:
asm volatile ( ... : ... : ... : ...);
如果擔心有變量衝突使用__volatile__關鍵字。
如果彙編語句只是做一些運算而沒有什麼附加影響。最好不要使用volatile。不用volatile時會給GCC做代碼優化留下空間。
在“常用技巧”章節中給出了很多例子,在那裏你也可以詳細看到clobber-list的使用。
6. 深入constraints
此時你可能理解了constraint對內聯彙編有很大的影響。但是我們到目前爲止才接觸到說了關於constraint的一小部分。constraint
可以指出一個操作數是在寄存器中,在那個寄存器中,指出操作數是一個內存引用或具體內存地址。無論操作數是直接常量,或者可能是什麼值。
6.1 常用constraints
雖然有很多constraints,但是常用的只有少數。下面我們就來看下這些限制條件。
1. 寄存器操作數限制條件: r
如果操作數指定了這個限制,操作數將使用通用寄存器來存儲。看下面的例子:
asm ( “movl %%eax, %0” : “=r” (myval));

量myval被保存在一個寄存器中,eax中的值被拷貝到這個寄存器中,並且在內存中的myval的值也會按這個寄存器值被更新。當
constraints ”r”
被指定時,GCC可能在任何一個可用的通用寄存器中保存這個值。當然如果你要指定具體使用那個寄存器就要指定具體使用哪個寄存器的
constraints。如下表:
r Register(s)
a %eax, %ax, %al
b %ebx, %bx, %bl
c %ecx, %cx, %cl
d %edx, %dx, %adl
S %esi, %si
D %edi, %di
2. 內存操作數constraint: m

操作數在內存中時,任何對其操作將直接通過內存地址進行。和寄存器constraint相反,內存操作是先把值存在一個寄存器中,修改後再將值回寫到這個
內存地址。寄存器constraint通常只用在對速度要求非常嚴格的場合。因爲內存constraint可以更有效率的將一個
C語言變量在asm中跟新[不需要寄存器中轉],而且可能你也不想用一個寄存器來暫存這個變量的值。例如:
asm (“sidt” %0” : : “m”(loc) );
3. 匹配constraint
在某些情況下,一個變量可能用來保存輸入和輸出兩種用途。這種情況下我們就用匹配constraint
asm (“incl %0” :”=a”(var) : “0”(var) );

們在之前章節中已經看過類似的例子。這個例子中eax寄存器被用來保存輸入也用來保存輸出變量。輸入變量被讀入eax中,incl執行之後eax被跟新並
且又保存到變量var中。這兒的constraint
”0”指定使用用和第一個輸出相同的寄存器。就是說,輸入的變量應該只能放在eax中。這個constraint可以在下面的情況下被使用:
a) 輸入值從一個變量讀入,這個變量將被修改並且修改過的值要寫回同一個變量;
b) 沒有必要把輸入和輸出操作數分開。
使用匹配constraint最重要的好處是對變量寄存器地使用更高效。
4. 其他constraint
1. “m”: 使用一個內存操作數,內存地址可以是機器支持的範圍內。
2. “o”: 使用一個內存操作數,但是要求內存地址範圍在在同一段內 。例如,加上一個小的偏移量來形成一個可用的地址。
3. “V”: 內存操作數,但是不在同一個段內。換句話說,就是使用”m”的所有的情況除了”o”
4. “i”: 使用一個立即整數操作數(值固定);也包含僅在編譯時才能確定其值的符號常量。
5. “n”: 一個確定值的立即數。很多系統不支持彙編時常數操作數小於一個字。這時候使用n就比使用i好。
6. “g”: 除了通用寄存器以外的任何寄存器,內存和立即整數。
5. 下面的是x86特有的constraint:
"r" : Register operand constraint, look table given above.
"q" : Registers a, b, c or d.
"I" : Constant in range 0 to 31 (for 32-bit shifts).
"J" : Constant in range 0 to 63 (for 64-bit shifts).
"K" : 0xff.
"L" : 0xffff.
"M" : 0, 1, 2, or 3 (shifts for lea instruction).
"N" : Constant in range 0 to 255 (for out instruction).
"f" : Floating point register
"t" : First (top of stack) floating point register            
"u" : Second floating point register
"A" : Specifies the `a’ or `d’
registers. This is primarily useful for 64-bit integer values intended
to be returned with the `d’ register holding the most significant bits
and the `a’ register holding the least significant bits.
6.2 constraint修改標記
在使用constraint的時候,爲了更精確的控制約束,GCC提供了一些修改標記,常用的 修改標記有:
1. “=”指這個操作數是隻寫的;之前保存在其中的值將廢棄而被輸出值所代替。
2.
“&” Means that this operand is an earlyclobber operand, which is
modified before the instruction is finished using the input operands.
Therefore, this operand may not lie in a register that is used as an
input operand or as part of any memory address. An input operand can be
tied to an earlyclobber operand if its only use as an input occurs
before the early result is written. [這段不太明白,以後明白了在翻譯]。
對constraint的解釋還遠沒有完。但是例子可以幫助我們更好的理解內聯彙編。下一小結中我們將來看一些例子。這裏例子中我們能看到更多的關於clobber-list和constraint。
7.常用技巧
到目前爲止,已經講完了GCC內聯彙編基礎知識。現在讓我們來看一些簡單的例子。 內聯彙編函數可以很方便通過宏的形式來寫。可以在內核代碼中看到很多這樣的內聯彙編函數(在/usr/src/linux/asm/*.h)
1. 我們從一個簡單的例子起。 我們來寫把兩個數字加起來的一個程序。
int main(void)
{
Int foo = 10, bar = 15;
__asm__ __volatile__ (“ addl %%ebx, %%eax”
: ”=a”(foo)
: ”a”(foo), “b”(bar)
);
prinft(“foo+bar=%d\n”, foo);
return 0;
}
這裏我們強制讓GCC將foo值存在%eax, bar 存在5ebx中,並且讓輸出放在%eax中。其中”=”表明這是一個輸出寄存器。再看看其他方法來加這兩個數。
__asm__ __volatile__ (
“lock;\n”
“addl %1,%0;\n”
:”=m”(my_var)
:”ir”(my_int), “m”(my_var)
:
);
這是一個原子加法操作。可以去除指令lock移除原子性。在輸出部分”=m”指出my_var作爲輸出並且在內存中。類似的”ir”指出my_int是一個整型數並且要保存到一個寄存器中(可以想象上面關於constraint的表)。這裏沒有clobber list
2. 我們在一些寄存器活變量上來執行一些動作來對比下這些值。
__asm__ __volatile__ ( “decl %0; sete %1”
: “=m” (my_var), “=q” (cond)
: “m” (my_var)
: ”memory”
);
上面的程序將my_var減一併且如果減一後結果爲零就將cond置位。我們可以再彙編語句之前加上”lock;\n\t”變成原子操作。
同樣,我們可以用”incl %0”來代替”decl %0”來增加my_var的值。
這裏值得注意的幾點是
1) my_var是一個存在內存中的變量。
2) cond是一個存在任何通用寄存器中(eax,ebx,ecx,edx)的這時由於限制條件”=q”決定的。
3) clobber list中指定了memory。說明代碼將改變內存值。
3. 如何設置和清除寄存器中的某一位?這就是下一個我們要看的技巧。
__asm__ __volatile__( “btsl %1, %0”
: “=m” (ADDR)
: “Ir” (pos)
: “cc”
);

裏在變量ADDR(一個內存變量)在’pos’的位置值被設置成了1.。我們可以時候btrl來清除由btsl設置的位.pos變量的限定字符”Ir”
指明pos放在寄存器中並且值爲0-31(I是一個x86相關constraint).例如我們可以設置或者清除ADDR變量中從第0到第31位的值。因
爲這個要改變其中的值,所以我們加上”cc”在clobberlist中
4. 現在我裏來看一些更加複雜但是有用的函數。字符串拷貝函數
static inline char* strcpy (char* dest, const char* src)
{
int d0, d1, d2;
__asm__ __volatile__( "1:\tlodsb\n\t"
"stosb\n\t"
"testb %%al,%%al\n\t"
"jne 1b"
: "=&S" (d0), "=&D" (d1), "=&a" (d2)
: "0" (src),"1" (dest)
: "memory");
return dest;
}

地址存在ESI寄存器中,目的地址存在EDI中。接着開始複製,直到遇到0結束複製。約束條件”&S”,”&D”,”&a”指明
我們使用的是ESI,EDI和EAX寄存器。並且這些寄存器是很明顯的clobber寄存器。("=&S" (d0), "=&D"
(d1), "=&a" (d2) 這裏用這三個寄存器作輸出,GCC很明顯知道他們將被clobber所以後面的clobber
list不用再寫了)它們的內容在函數執行後會改變。這裏還有很明顯可以看出爲什麼memory被放在clobber list中。 (d0, d1,
d2被更新)
我們再來看一個相似的函數,用來移動一塊雙字。注意這個函數通過宏來定義的。
#define mov_blk(src, dest, numwords) \
__asm__ __volatile__ ( \
"cld\n\t" \
"rep\n\t" \
"movsl" \
: \
: "S" (src), "D" (dest), "c" (numwords) \
: "%ecx", "%esi", "%edi" \
)
這裏沒有輸出,塊移動過程導致ECX,ESI,EDI內容的改變,所以我們必須把它們放在clobber list中。
在linux中,系統調用是用 GCC內聯彙編的形式實現的。就讓我們來看看一個系統調用是如何實現的。所有的系統調用都是用宏來寫的(linux/unistd.h). 例如,一個帶三個參數的系統調用的定義如下:
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
一旦一個帶三個參數的系統調用發生,上面的這個宏用來執行系統調用。系統調用號放在eax中,每個參數放在ebx,ecx,edx中,最後”int 0x80”執行系統調用。返回值放在eax中。
所有的系統調用都是用上面類似的方式實現的.Exit是帶一個參數的系統調用。我們看下這個實現的代碼,如下:
{
asm("movl $1,%%eax; /* SYS_exit is 1 */
xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */
int $0x80" /* Enter kernel mode */
);
}
Exit的調用號是1參數爲0,所以我們把1放到eax中和把0放到ebx中,通過int $0x80 exit(0)就被執行了。
這就是exit如何工作的。
8.結束語
這篇文章講述了GCC內聯彙編的基礎內容。一旦你理解了基礎原則,你自己一步步的看下去就沒有什麼困難了。我們通過一些例子可以更好的幫助我們理解一些在內聯彙編中常用特性。
GCC內聯是一個很大的主題,這片文章要講的還遠遠不夠。但大多數我們提到的語法都可以在官方文檔GNU Assembler中看到。完整的constraint可以在GCC官方文檔中找到。
當然Linux內核大範圍內使用了GCC內聯。因此我們可以從中找到各種各樣的例子。這對我們很有幫助。
如果你找到任何低級的排版打字錯誤或者過時的信息,請聯繫我。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章