在 C++ 剛出現的時候,大多數 C++ compiler 引入了一個新關鍵字 'inline' ,用於解決個體行為 (methods of object) 的部份效率瓶頸。直到 ISO C99 時,才正式將 inline 定於規範之中。不過多數的 C/C++ compiler 只將 inline 用於 C++ program 中,並未延伸到 C program (*1)。'inline' is a keyword of ANSI/ISO C99. Some C/C++ compilers do not allow to use this in C program. '__inline__' is a keyword of GNU GCC. Some compilers use '__inline'.
)。
GCC 最晚在 2.9 版 ,為 C program 加入了一個擴充關鍵字 '__inline__' (*2),使得 programmer 可以在 C program 中使用 inline function 。請參考 GNU GCC Info document setcion 'An Inline Function is As Fast As a Macro'. 說明文件意簡言骸,我將配合程式碼說明 inline function 在 C program 中運作的相關細節。
-
GCC 為 C program 提供了 __inline__ 擴充關鍵字,有些 compiler 則是用 __inline。
-
印象中,我在 1998-99 年時,便已在 C program 中使用了 __inline__ 關鍵字,見 FBTIP。因此 GCC 是在 ISO C99 規範出現前,先提供了這個擴充關鍵字供 programmer 使用。
Codes of Case
c_inline_case1.h
Case 1, combination of inline and static.
c_inline_case2.h
Case 2, combination of inline and extern.
c_inline_case3.h
Case 3, inline only.
strcase.h
case_l.c
Call inline function 'lcase' directly. 第 19 行展示了 inline function 可以回傳值的特性。用 MACRO 的方式,在此無法通過。
case_u.c
Call inline function 'ucase' via pointer of function. 第 17 行展示了 inline function 可以用於函數指標、遞迴的特性。同樣地,用 MACRO 也無法做到。
Works only in optimizing compilation
Inlining of functions is an optimization and it really "works" only in optimizing compilation. If you don't use '-O', no function is really inline.
When a function is both inline and static, if all calls to the function are integrated into the caller, and the function's address is never used, then the function's own assembler code is never referenced. In this case, GCC does not actually output assembler code for the function, unless you specify the option `-fkeep-inline-functions'. Some calls cannot be integrated for various reasons (in particular, calls that precede the function's definition cannot be integrated, and neither can recursive calls within the definition). If there is a nonintegrated call, then the function is compiled to assembler code as usual. The function must also be compiled as usual if the program refers to its address, because that can't be inlined.
An Inline Function is As Fast As a Macro
以 '-S' 指示 gcc 將程式碼譯為組合語言碼 (assembler code) ,以便觀察有無啟用最佳化選項 (-O) 的影嚮。
# gcc -DUSE_CASE1 -S -o case_l_1a.asm case_l.c
# gcc -DUSE_CASE1 -O -S -o case_l_1b.asm case_l.c
# gcc -DUSE_CASE1 -O -S -o case_u_1b.asm case_u.c
case_l_1a.asm
第 39-69 行是 lcase 的 assembler code ,第 32 行顯示此處依然使用 call 調用 lcase ,而非直接展開 (inline) 。這表示在未啟用最佳化選項時, GCC 不會展開 inline function 的內容。
case_l_1b.asm
第 17-39 行是 lcase 展開後的內容,這表示 GCC 啟用最佳化選項時,才會展開 inline function 。由於同時搭配 static 關鍵字,在沒有需要時, GCC 不會產生 assembler code ,所以在此看不到 lcase 的 assembler code (In this case, GCC does not actually output assembler code for the function
)。
case_u_1b.asm
儘管啟用了最佳化選項,但此例是透過函數指標 (pointer of function) 調用 ucase ,所以第 28-55 行中, GCC 依然產生了 ucase 的 assembler code (The function must also be compiled as usual if the program refers to its address, because that can't be inlined.
)。 GCC 仍然會在可以展開之處展開 inline function ,但在不能展開之處,則用 call 調用。在第 21 行中,便是用 call 調用 ucase 。可以注意到,第 21 行並不是透過原先指定的變數,而是直接取用 ucase 的 address ,這是因為在最佳化情形下, GCC 判斷可以直接取用 ucase 的 address 。
Combination of inline and extern
If you specify both inline and extern in the function definition, then the definition is used only for inlining. In no case is the function compiled on its own, not even if you refer to its address explicitly. Such an address becomes an external reference, as if you had only declared the function, and had not defined it.
This combination of inline and extern has almost the effect of a macro.
An Inline Function is As Fast As a Macro
當 inline 和 extern 搭配使用時, GCC 完全不產生 inline function 的 assembler code ,即使是無法展開 inline function 的場合,也僅僅試圖以外部符號調用外部的 assembler code 。因此在此例中,雖可通過 compile ,但 linker 會丟出參照符號未定義的錯誤。
# gcc -DUSE_CASE2 -O -o case_u_2 case_u.c
ccymaaaa.o(.text+0x40):case_u.c: undefined reference to 'ucase'
gcc: ld returned 1 exit status
# gcc -DUSE_CASE2 -O -S -o case_u_2.asm case_u.c
case_u_2.asm
此例係透過函數指標調用 ucase ,無法直接展開。故在第 28 行中宣告 ucase 是一個外部符號,第 21 行中用 call 調用。完全不產生 ucase 的 assembler code ,而認為其可透過連結其他 object files 獲得。
Only inline
When an inline function is not static, then the compiler must assume that there may be calls from other source files; since a global symbol can be defined only once in any program, the function must not be defined in the other source files, so the calls therein cannot be integrated. Therefore, a non-static inline function is always compiled on its own in the usual fashion.
An Inline Function is As Fast As a Macro
單獨使用 inline 時, GCC 一定會產生 inline function 的 assembler code ,並宣告為全域符號 (global symbol) 。在此例中,其立即而明顯的問題在於,每一個引入 strcase.h 的 C source files ,其 object file 中都會包含相同的全域符號 'lcase' 和 'ucase' ,在連結多個 object files 時, linker 會丟出符號定義重覆的錯誤。
# gcc -DUSE_CASE3 -O -S -o case_l_3.asm case_l.c
case_l_3.asm
第 5-32 行是 lcase 的 assembler code ,第 3 行宣告 'lcase' 為全域符號 (global symbol)。第 35-62 行是 ucase 的 assembler code ,第 33 行宣告 'ucase' 為全域符號 (global symbol)。第 77-99 行處,由於可以展開 inline function ,故展開之。
我當年 (大約十年以前) 曾在 80x86 CPU 和 DOS 環境下,用 Microsoft MASM 開發給 QuickBASIC 和 Turbo C 調用的 function library 。記得關於混合語言的使用方法 (BASIC, C, 組合語言互相呼叫彼此的 functuions) ,大約十幾年前還有蠻多資料可參考的。但那早在 Internet 興起前,如今想在網路上找到這類資料,並不容易。自己跑一趟學校的圖書館還快些。
回到正題。你可以先試試用 ANSI C/C++ 的 inline assembler 功能,關鍵字是 asm (或 __asm) 。
一般來說,不論用什麼程式語言,我們在 make 程式時首先需由 compiler/assembler 編譯或組譯產生出 obj file ,再由 linker 將 obj files 連結起來。這部份的操作過程以及 obj file format ,要看你的開發工具使用手冊。例如 GCC 開發工具的操作過程如下所示。 gcc 是一個 front-end tool ,根據來源檔副檔名決定處理動作, .c 是調用 C compiler , .as 是調用 assembler , .o 則是調用 linker 。
$ gcc -c -o a.o a.c
$ gcc -c -o b.o b.as
$ gcc -o c a.o b.o
在程式碼的編寫上, C 語言在呼叫外部程式的 function 時,須用 extern "C" {} 括住外部程式的函數宣告,以抑制 C/C++ compiler 對函數名稱符號 (symbol) 的修改動作。在組合語言方面,函數名稱符號要宣告為 global ,還要配合 C 語言的函數呼叫慣例處理 stack 和回傳值。以 80x86 CPU 為例,參數由 C 語言負責放在 stack segment 中,並由 C 語言這方負責清除;函數回傳值要放在 ax/eax register ;返回 (ret) 之前不需要清除 stack 等等。這方面同樣要參閱晶片架構和開發工具手冊的說明。
我想
NASM Manual 中,有些資料可以參考。