gcc的__attribute__編譯屬性

要了解Linux Kernel代碼的分段信息,需要了解一下gcc的__attribute__的編繹屬性,__attribute__主要用於改變所聲明或定義的函數或數據的特性,它有很多子項,用於改變作用對象的特性。比如對函數,noline將禁止進行內聯擴展、noreturn表示沒有返回值、pure表明函數除返回值外,不會通過其它(如全局變量、指針)對函數外部產生任何影響。但這裏我們比較感興趣的是對代碼段起作用子項section。 

__attribute__的section子項的使用格式爲: 

__attribute__((section("section_name"))) 

其作用是將作用的函數或數據放入指定名爲"section_name"輸入段。 

這裏還要注意一下兩個概念:輸入段和輸出段 

輸入段和輸出段是相對於要生成最終的elf或binary時的Link過程說的,Link過程的輸入大都是由源代碼編繹生成的目標文件.o,那麼這些.o文件中包含的段相對link過程來說就是輸入段,而Link的輸出一般是可執行文件elf或庫等,這些輸出文件中也包含有段,這些輸出文件中的段就叫做輸出段。輸入段和輸出段本來沒有什麼必然的聯繫,是互相獨立,只是在Link過程中,Link程序會根據一定的規則(這些規則其實來源於Link Script),將不同的輸入段重新組合到不同的輸出段中,即使是段的名字,輸入段和輸出段可以完全不同。 

其用法舉例如下: 

int var __attribute__((section(".xdata"))) = 0; 

這樣定義的變量var將被放入名爲.xdata的輸入段,(注意:__attribute__這種用法中的括號好像很嚴格,這裏的幾個括號好象一個也不能少。) 

static int __attribute__((section(".xinit"))) functionA(void) 



..... 


這個例子將使函數functionA被放入名叫.xinit的輸入段。 

需要着重注意的是,__attribute__的section屬性只指定對象的輸入段,它並不能影響所指定對象最終會放在可執行文件的什麼段。 

2. Linux Kernel源代碼中與段有關的重要宏定義 

A. 關於__init、__initdata、__exit、__exitdata及類似的宏 

打開Linux Kernel源代碼樹中的文件:include/init.h,可以看到有下面的宏定議: 

#define __init __attribute__ ((__section__ (".init.text"))) __cold 

#define __initdata __attribute__ (( __section__ (".init.data"))) 

#define __exitdata __attribute__ (( __section__ (".exit.data"))) 

#define __exit_call __attribute_used__ __attribute__ (( __section__ (".exitcall.exit"))) 

#define __init_refok oninline __attribute__ ((__section__ (".text.init.refok"))) 

#define __initdata_refok __attribute__ ((__section__ (".data.init.refok"))) 

#define __exit_refok noinline __attribute__ ((__section__ (".exit.text.refok"))) 

......... 

#ifdef MODULE 

#define __exit __attribute__ (( __section__ (".exit.text"))) __cold 

#else 

#define __exit __attribute_used__ __attribute__ ((__section__ (".exit.text"))) __cold 

#endif 

對於經常寫驅動模塊或翻閱Kernel源代碼的人,看到熟悉的宏了吧:__init, __initdata, __exit, __exitdata。 

__init 宏最常用的地方是驅動模塊初始化函數的定義處,其目的是將驅動模塊的初始化函數放入名叫.init.text的輸入段。對於__initdata來說,用於數據定義,目的是將數據放入名叫.init.data的輸入段。其它幾個宏也類似。另外需要注意的是,在以上定意中,用__section__代替了section。還有其它一些類似的宏定義,這裏不一一列出,其作用都是類似的。 

B. 關於initcall的一些宏定義 

在該文件中,下面這條宏定議更爲重要,它是一條可擴展的宏: 

#define __define_initcall(level,fn,id) \ 

static initcall_t __initcall_##fn##id __attribute_used__ \ 

__attribute__ ((__section__(".initcall" level ".init"))) = fn 

這條宏帶有3個參數:level,fn, id,分析該宏可以看出: 

 1.其用來定義類型爲initcall_t的static函數指針,函數指針的名稱由參數fn和id決定:__initcall_##fn##id,這就是函數指針的名稱,它其實是一個變量名稱。從該名稱的定義方法我們其學到了宏定義的一種高級用法,即利用宏的參數產生名稱,這要藉助於"##"這一符號組合的作用。 

 2. 這一函數指針變量放入什麼輸入段呢,請看__attribute__ ((__section__ (".initcall" levle ".init"))),輸入段的名稱由level決定,如果level="1",則輸入段是.initcall1.init,如果level="3s",則輸入段是.initcall3s.init。這一函數指針變量就是放在用這種方法決定的輸入段中的。 



關鍵字__attribute__允許你在聲明時指定特殊的屬性。跟在這個關鍵字後面的是雙重圓括號裏面的屬性說明。有十四個屬性noreturn, pure, const, format, format_arg, no_instrument_function, section, constructor, destructor, unused, weak, malloc, alias and no_check_memory_usage是目前爲函數定義的。在特別的目標系統上,也給函數定義了一些其它屬性。其它屬性,包括section都爲變量聲明(參考5.33節 指定變量屬性)和類型(參考5.34節 指定類型屬性)所支持。
你也可以把“__”放在每個關鍵字的前面和後面來指定屬性。這允許你在頭文件中使用它們,而不用關心一個可能有相同名字的宏。比如,你可以使用__noreturn__而不是noreturn。
參見5.27節 屬性語法來了解使用屬性的精確語法細節。
noreturn
一些標準庫函數,就像abort和exit,不能返回。GCC會自動瞭解到這一點。一些程序定義它們自己的從不返回的函數。你可以把它們聲明爲noreturn來告訴編譯器這個事實。比如,
  
void fatal () __attribute__ ((noreturn)); void fatal (...) { ... /* Print error message. */ ... exit (1); } 
關鍵字noreturn告訴編譯器去假設fatal不能返回。那它就能做優化,而不用理會如果fatal返回會發生什麼。這會產生稍微好一點兒的代碼。更重要的是,它有助於避免未初始化變量的僞造警告。
不要假設調用函數保存的寄存器在調用noreturn函數之前被恢復。
對於一個noreturn函數,有一個除void之外的返回類型是毫無意義的。
在早於2.5版的GCC中沒有實現noreturn屬性。聲明不返回值的函數的一個可替代的方法,在當前版本和一些舊的版本中都可以工作,如下:
  typedef void voidfn (); volatile voidfn fatal; 
pure
很多函數除了返回值外沒有作用,而且它們的返回值只取決於參數和/或全局變量。這樣的一個函數可能依附於普通的子表達式的消除和循環的優化,就像一個算術操作符那樣。這些函數應該用屬性pure來聲明。例如,
  int square (int) __attribute__ ((pure)); 
說明假定的函數square可以安全地比程序中說的少調用幾次。(在循環中保存計算結果,而不必每次都計算)
pure函數的一些常見例子是strlen和memcmp。有趣的非pure函數是帶無限循環,或者那些取決於易失性內存或其它系統資源的函數,它們可能在兩次連續的調用中間改變(比如在多線程環境中的feof)。pure屬性在GCC早於2.96的版本中沒有實現。
const
該 屬性只能用於帶有數值類型參數的函數上。當重複調用帶有數值參數的函數時,由於返回值是相同的,所以此時編譯器可以進行優化處理,除第一次需要運算外,其 它只需要返回第一次的結果就可以了,進而可以提高效率。該屬性主要適用於沒有靜態狀態(static state)和副作用的一些函數,並且返回值僅僅依賴輸入的參數。
很多函數不檢查除它們的參數外的任何值,而且除返回值外沒有任何作用。基本上,這比上面的pure屬性稍微更嚴格一些,既然函數不允許去讀全局內存。
注意,帶指針參數,而且檢查所指向數據的函數不能聲明爲const。同樣的,調用非const函數的函數通常也不能是const。一個const函數返回void是沒任何意義的。
屬性const在GCC早於2.5的版本中沒有實現。聲明一個函數沒有副作用的一個可替代的方式,能夠在當前版本和一些舊的版本中工作,如下:
  typedef int intfn (); extern const intfn square; 
這種方法在2.6.0以後的GNU C++不起作用,既然語言指明const必須依附於返回值。
format (archetype, string-index, first-to-check)
format屬性指明一個函數使用printf,scanf,strftime或strfmon風格的參數,應該通過格式化字符串進行類型檢查。比如,聲明:
  extern int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3))); 
會促使編譯器檢查調用my_printf中的參數和printf風格的格式化字符串參數my_format是否一致。
參數archetype決定格式化字符串是怎麼被解釋的,而且應當是printf,scanf,strftime或strfmon。(你也可以使用__printf__,__scanf__,__strftime__或者__strfmon__。)參數string-index指定哪個參數是格式化字符串參數(從1開始),而first-to-check是通過格式化字符串檢查的第一個參數。對於參數不可用來檢查的函數(比如vprintf),指定第三個參數爲0。在這種情況下,編譯器只檢查格式化字符串的一致性。對於strftime格式,第三個參數需要爲0。
在上面的例子中,格式化字符串(my_format)是my_printf函數的第二個參數,而且要檢查的函數從第三個參數開始,所以format屬性的正確參數是2和3。
format屬性允許你去識別你自己的把格式化字符串作爲參數的函數,所以GCC可以檢查對這些函數的調用錯誤。編譯器總是(除非使用了“-ffreestanding”)爲標準庫函數printf,fprintf,sprintf,scanf,fscanf,sscanf,strftime,vprintf,vfprintf和vsprintf檢查格式,當請求這種警告時(使用“-Wformat”),所以沒有必要修改頭文件stdio.h。在C99模式下,函數snprintf,vsnprintf,vscanf,vfscanf和vsscanf也被檢查。參考“控制C方言的選項”一節。。
format_arg (string-index)
format_arg屬性指明一個函數使用printf,scanf,strftime或strfmon風格的參數,而且修改它(比如,把它翻譯成其它語言),所以結果能夠傳遞給一個printf,scanf,strftime或strfmon風格的函數(格式化函數的其餘參數和它們在不修改字符串的函數中一樣)。例如,聲明:
  extern char * my_dgettext (char *my_domain, const char *my_format) __attribute__ ((format_arg (2))); 

促使編譯器檢查調用printf,scanf,strftime或strfmon類型的函數中的參數,其格式化字符串參數是函數my_dgettext函數的調用,和格式化字符串參數my_format是否一致。如果format_arg屬性沒有被指定,在對格式化函數的這種中,編譯器所能告知的一切是格式化字符串參數不是常量;當使用“-Wformat-nonliteral”時,這會產生一個警告,但如果沒有屬性,調用將不會被檢查。
參數string-index指定哪個參數是格式化字符串(從1開始)。
format-arg屬性允許你去識別你自己的修改格式化字符串的函數,那樣,GCC可以檢查對printf,scanf,strftime或strfmon類型函數的調用,它們的操作數是對你自己的一個函數的調用。編譯器總是以這種方式對待gettext,dgettext和 dcgettext,除了當嚴格的ISO C支持通過“-ansi”或者一個合適的“-std”選項請求時,或者“-ffreestanding”使用時。參考“控制C方言的選項”一節。
no_instrument_function
如果給定“-finstrument-functions”,在大多數用戶編譯的函數的入口和出口會生成對概要分析函數的調用。有這個屬性的函數將不會被測量。
section ("section-name")
通常,編譯器會把它生成的代碼放入text部分。有時,然而,你需要額外的部分,或者你需要某些特別的函數出現在特別的部分。section屬性指定一個函數放入一個特別的部分。比如,聲明:
  extern void foobar (void) __attribute__ ((section ("bar"))); 
把函數foobar放進bar部分。
一些文件格式不支持任意部分,所以section屬性並不是在所有平臺上可用的。如果你需要把一個模塊的全部內容映射到一個特別的部分,考慮使用鏈接器的工具。
constructor
destructor
constructor屬性促使函數在執行main()之前自動被調用。類似地,destructor屬性促使函數在main()函數完成或exit()被調用完之後自動被調用。有這些屬性的函數對初始化在程序執行期間間接使用的數據很有用。
這些屬性目前沒有爲Objective C所實現。
unused
這個屬性,依附於一個函數,意味着這個函數將可能打算不被使用。GCC將不會爲這個函數產生一個警告。GNU C++目前不支持這個屬性,因爲沒有參數的定義在C++中是合法的。
weak
weak屬性促使聲明被作爲一個弱符號導出,而不是全局符號。這在定義庫函數時非常有用,它們能夠被用戶代碼覆蓋,雖然它也可以和非函數聲明一起使用。弱符號被ELF目標文件所支持,而且當使用GNU彙編器和鏈接器時也被a.out目標文件支持。
malloc
malloc屬性用來告訴編譯器一個函數可以被當做malloc函數那樣。編譯器假設對malloc的調用產生一個不能替換成其它東西的指針。
alias ("target")
alias屬性促使這個聲明被作爲另一個必須被指定的符號的別名導出。例如,
  void __f () { /* do something */; } void f () __attribute__ ((weak, alias ("__f"))); 
聲明“f”是“__f”的一個弱別名。在C++中,目標的重整名字必須被使用。
並不是所有的目標機器支持這個屬性。
no_check_memory_usage
no_check_memory_usage屬性促使GCC忽略內存引用的檢查,當它爲函數生成代碼時。通常如果你指定“-fcheck-memory-usage”(參考“t/newbie/gcc.htm#3.18">3.18 代碼生成轉換選項”一節),GCC在大多數內存訪問之前生成調用支持的例程,以便允許支持代碼記錄用法和探測未初始化或未分配存儲空間的使用。既然GCC不能恰當處理asm語句,它們不允許出現在這樣的函數中。如果你用這個屬性聲明瞭一個函數 ,GCC將會爲那個函數生成內存檢查代碼,允許使用asm語句,而不必用不同選項去編譯那個函數。這允許你編寫自己的支持例程如果你願意,而不會導致無限遞歸,如果它們用“-fcheck-memory-usage”編譯的話。
regparm (number)
在Intel 386上,regparm屬性促使編譯器用寄存器EAX,EDX,和ECX,而不是堆棧,來傳遞最多number個參數。帶可變參數數目的函數將會繼續在堆棧上傳遞它們的參數。
stdcall
在Intel 386上,stdcall屬性促使編譯器假定被調用的函數將會彈出用來傳遞參數的堆棧空間,除非它適用可變數目的參數。
cdecl
在Intel 386上,cdecl屬性促使編譯器假定調用函數將會彈出用來傳遞參數的堆棧空間。這對覆蓋“-mrtd”開關的作用很有幫助。
PowerPC上Windows NT的編譯器目前忽略cdecl屬性。
longcall
在RS/6000和PowerPC上,longcall屬性促使編譯器總是通過指針來調用函數,所以在距當前位置超過64MB(67108864字節)的函數也能夠被調用。
long_call/short_call
這個屬性允許指定如果在ARM上調用一個特別的函數。兩個屬性都覆蓋“-mlong-calls”命令行開關和#pragma long_calls設置。long_call屬性促使編譯器總是通過先裝入它的地址到一個寄存器再使用那個寄存器的內容來調用這個函數。short_call屬性總是直接把從調用現場到函數的偏移量放進‘BL’指令中。
dllimport
在運行Windows NT的PowerPC上,dllimport屬性促使編譯器通過一個全局指針去調用函數,這個指針指向由Windows NT的dll庫安裝的函數指針。指針名是通過組合__imp__和函數名來形成的。
dllexport
在運行Windows NT的PowerPC上,dllexport屬性促使編譯器提供一個指向函數指針的全局指針,那樣它就能用dllimport屬性調用。指針名是通過組合__imp__和函數名來形成的。
exception (except-func [, except-arg])
在運行Windows NT的PowerPC上,exception屬性促使編譯器修改爲聲明函數導出的結構化異常表的表項。字符串或標識符except-func被放在結構化異常表的第三項中。它代表一個函數,當異常發生時被異常處理機制調用。如果它被指定,字符串或標識符except-arg被放在結構化異常表的第四項中。
function_vector
在H8/300和H8/300H上使用這個選項用表明指定的函數應該通過函數向量來調用。通過函數向量調用函數將會減少代碼尺寸,然而,函數向量有受限的大小(在H8/300上最多128項,H8/300H上64項),而且和中斷向量共享空間。
爲此選項,你必須使用2.7版或以後的GNU binutils中的GAS和GLD才能正確工作。
interrupt
在H8/300,H8/300H和SH上使用這個選項表明指定的函數是一箇中斷處理程序。當這個屬性存在時,編譯器將會生成函數入口和出口工序,爲適應在中斷處理程序中的使用。
注意,H8/300,H8/300H和SH處理器的中斷處理程序可以通過interrupt_handler屬性來指定。
注意,在AVR上,中斷將會在函數裏面被啓用。
注意,在ARM上,你可以通過給中斷屬性添加一個可選的參數指定要處理的中斷類型,就像這樣:
void f () __attribute__ ((interrupt ("IRQ")));
這個參數的允許值是:IRQ, FIQ, SWI, ABORT和UNDEF。
interrupt_handler
在H8/300,H8/300H和SH上使用這個選項表明指定的函數是一箇中斷處理程序。當這個屬性存在時,編譯器將會生成函數入口和出口工序,爲適應在中斷處理程序中的使用。
sp_switch
在SH上使用這個選項表明一個interrupt_handler函數應該切換到一個可替代的堆棧上。它期待一個字符串參數,用來命名一個存放替代堆棧地址的全局變量。
  void *alt_stack; void f () __attribute__ ((interrupt_handler, sp_switch ("alt_stack"))); 
trap_exit
在SH上爲interrupt_handle使用此選項來使用trapa而不是rte來返回。這個屬性期待一個整數參數來指定要使用的陷阱號。
eightbit_data
在H8/300和H8/300H上使用此選項來表明指定的變量應該放到8比特數據區。編譯器將會爲在8比特數據區上的操作生成更高效的代碼。注意8比特數據區的大小限制在256字節。
tiny_data
在H8/300H上使用此選項來表明指定的變量應該放到微數據區。編譯器將會爲在微數據區中存取數據生成更高效的代碼。注意微數據區限制在稍微低於32K字節。
signal
在AVR上使用此選項來表明指定的函數是一個信號處理程序。當這個屬性存在時,編譯器將會生成函數入口和出口工序,爲適應在信號處理程序中的使用。在函數內部,中斷將會被屏蔽。
naked
在ARM或AVR移植上使用此選項來表明指定的函數不需要由編譯器來生成開場白/收場白工序。由程序員來提供這些工序。
model (model-name)
在M32R/D上使用這個屬性來設置對象和函數生成代碼的可尋址性。標識符model-name是small,medium或large其中之一,各代表一種編碼模型。
small模型對象駐留在內存的低16MB中(所以它們的地址可以用ld24指令來加載),可用bl指令調用。
medium模型對象可能駐留在32位地址空間的任何地方(編譯器將會生成seth/add3指令來加載它們的地址),可用bl指令調用。
large模型對象可能駐留在32位地址空間的任何地方(編譯器將會生成seth/add3指令來加載它們的地址),而且可能使用bl指令夠不到(編譯器將會生成慢得多的seth/add3/jl指令序列)。
你可以在一個聲明中指定多重屬性,通過在雙圓括號中用逗號來把它們分割開,或者在一個屬性聲明後緊跟另一個屬性聲明。
一些人反對__attribute__特性,建議使用ISO C的#pragma來替代。在設計__attribute__時,有兩條原因不適合這麼做。
不可能從宏中生成#pragma命令。
沒有有效說明同樣的#pragma在另一個編譯器中可能意味着什麼。 
這兩條原因適用於幾乎任何提議#pragma的應用程序。爲任何東西使用#pragma基本上都是一個錯誤。
ISO C99標準包括_Pragma,它現在允許從宏中生成pragma。另外,#pragma GCC名字空間現在爲GCC特定的pragma使用。然而,人們已經發現使用__attribute__來實現到相關聲明的自然的附件屬性很方便,而爲構造而使用的#pragma GCC沒有自然地形成語法的一部分。查看“C預處理器”中的“多種預處理命令”一部分。
5.27 屬性語法
這一段說明了在C語言中,使用到__attribute__的語法和屬性說明符綁定的概念。一些細節也許對C++和Objective C有所不同。由於對屬性不合適的語法,這裏描述的一些格式可能不能在所有情況下成功解析。
看5.26節,聲明函數的屬性,瞭解施加於函數的屬性語義的細節。看5.33節,說明變量屬性,瞭解施加於變量的屬性語義的細節。看5.34節指定類型屬性,瞭解施加與結構體,共用體,和枚舉類型的屬性語義的細節。
屬性說明符是的格式是:__attribute__((屬性列表))。屬性列表是一個可爲空的由逗號分隔的屬性序列,其中的屬性類型如下:
空。空屬性會被忽略。
字(可能是一個標識符如unused,或者一個保留字如const)。
在跟在後邊的圓括號中有屬性的參數的字。這些參數有如下的格式:
標識符。比如,模式屬性使用這個格式。 
跟有逗號或非空的由逗號分隔的表達式表。比如,格式化屬性使用這個格式。 
可爲空的由逗號分隔的表達式表。比如,格式化參數使用這個格式和一個單獨整型常量表達式列表,並且別名屬性使用這個格式和一個單獨的字符串常量列表。
屬性說明符列表是一個由一個或多個屬性說明符組成的序列,不被任何其它標誌分開。
屬性說明符列表可能跟在一個標籤後的冒號出現,除了case或default中的標籤。唯一的屬性使用在一個未使用的標籤後是合理的。這個特徵被設計成代碼被包含而未使用的標籤但是在編譯是使用了"-Wall"參數。它也許不常用與人工編寫的代碼中,雖然它應該在代碼需要跳到一個包含在一段有#ifdef說明的條件編譯的程序段中很有用。
屬性說明符列表可以作爲結構體、共用體或枚舉說明符的一部分出現。它既可以直接跟在結構體、共用體或枚舉說明符後,也可以緊貼大括號之後。如果結構體、共用體或枚舉類型沒有在使用屬性說明符列表中用到的說明符定義,也就是說在如下這樣的用法中struct __attribute__((foo))沒有跟空的大括號,它會被忽略。在用到屬性說明符的地方,跟它靠近的大括號,它們會被認爲和結構體、共用體或被定義的枚舉類型有聯繫,不同任何出現在包含聲明的類型說明符,並且類型直到屬性說明符之後才結束。
否則,屬性說明符作爲聲明的一部分出現,計算未命名參數和類型明聲明,並且和這個聲明相關(有可能內嵌在另外一個聲明中,例如在參數聲明的時候)。 將來屬性說明符在一些地方也任何用於特殊聲明符不超出代替聲明;這些情況將在一下提到。在屬性說明符被用於在函數或數組中說明參數的地方,它也許用於函數 或數組而不是指向一個會被隱含轉換的參數的指針,但這不僅是正確的工具。
任何在可能包含屬性說明符的聲明開始處的說明符與修飾符列表,抑或沒有這樣一個列表也許在上下文包含存儲類說明符。(一些屬性儘管本質自然是類型說 明符,並且僅在需要使用存儲類說明符的地方是合理的,例如section。)對這個語法有一個必要的限制:第一,在函數定義中的老式風格的參數聲明無法有 屬性說明符開頭,這種情況尚不提供)。在一些其它情況下,屬性說明符被允許使用這種語法但不被編譯器所支持。所有的屬性說明符在這裏被當做正割聲明的一部 分。在逐步被廢棄的在int類型默認省略了類型說明符的用法的地方,這樣的說明符和修飾符列表可能是沒有任何其它說明符或修飾符的屬性說明符列表。
在不止一個標識符使用單獨的說明符或修飾符的聲明中的由逗號分隔的說明符列表中,屬性說明符列表可能直接在一個說明符之前出現(第一個除外)。目 前,這樣的屬性說明符不僅適用於被出現在自己的聲明中的標識符,也可以用於在此聲明明中此後聲明的所有標識符,但是將來它們可能只能用於那樣的單獨的標識 符。例如:__attribute__((noreturn)) void d0 (void), __attribute__((format(printf, 1, 2))) d1 (const char *, ...), d2 (void)
無返回值屬性用於所有的函數聲明中;格式化屬性會只用於d1,但是目前也用於d2(並且由此造成錯誤)。
屬性說明符列表可能直接出現在逗號、等號或分號之前,終止標識符的說明除過函數定義。目前,這樣的屬性說明符用於已聲明的對象或函數,但是將來它們將附屬與相鄰的最遠的說明符。在簡單情況下沒有區別,但是例如在void (****f)(void) __attribute__((noreturn))這 樣的聲明中,當前無返回值的屬性用於f,這就會造成從f起不是一個函數的警告,但是將來它也許用於函數****f。在這種情況中的屬性的明確的語言符號將 不用於定義。在對象或函數的彙編名稱處被說明(看5.37節控制用於彙編代碼的名稱),當前,屬性必須跟隨與asm說明之後;將來,在asm說明之前的屬 性可能用於鄰近的聲明符,並且它那些在它之的被用於已聲明的對象或函數。
將來,屬性說明符可能被允許在函數定義的聲明符後出現(在任意老式風格參數聲明或函數題之前)。
屬性說明符列表可能出現在內嵌聲明符的開始。目前,這種用法有一些限制:屬性用於在這個聲明中聲明過的標識符和所有之後的聲明過的標識符(如果它包 括一個逗號分隔的聲明符列表),而不僅是用於特定的聲明符。當屬性說明符跟在指針聲明符“*”之後時,它們應該出現在任意一種修飾符序列之後,並且不能和 它們混在一起。接下來的陳述預期將來的語言符號僅使這種語法更有用。如果你對標準ISO C的聲明符的說明格式熟悉的話,它將更好理解。
考慮T D1這樣的聲明(像C99標準6.7.5第四段中的子句),T包含聲明說明符的地方說明一個Type類型(比如int)並且D1是一個包含標識符的標誌的聲明符。類型位標誌被說明用來導出類型不包括在屬性說明符中的聲明符就像標準ISO C那樣。
如果D1有(屬性說明符列表 D)這樣的格式,並且T D這樣的聲明爲標誌說明了“導出聲明符類型列表 類型”的類型,這樣T D1爲標誌說明了“導出聲明符類型列表 屬性說明符列表 類型”的類型。
如果D1有 * 類型修飾符和屬性說明符列表 D這樣的格式,並且T D這樣的聲明爲標誌說明“導出聲明符類型列表 類型”的類型,則T D1爲標誌說明“導出聲明符列表 類型修飾符和屬性說明符列表 類型”的類型。
例如,void (__attribute__((noreturn)) ****f)();說明“指向返回空的無返回值函數的指針的指針的指針”的類型。作爲另外的例子,char *__attribute__((aligned(8))) *f;說明“指向8位寬度的指向char型數據的指針的指針”的類型。再次注意這個陳述是被期待將來的語法符號,不是當前實現的。
5.28 原型和老式風格的函數定義
GNU C對ISO C到允許函數原型忽略一個新的老式風格的無原型定義。考慮一下的例子:
/* 除非老式的編譯器才使用原型*/
  /* Use prototypes unless the compiler is old-fashioned. */ #ifdef __STDC__ #define P(x) x #else #define P(x) () #endif /* 原型函數聲明. */ int isroot P((uid_t)); /* 老式風格的函數定義. */ int isroot (x) /* ??? lossage here ??? */ uid_t x; { return x == 0; } 
設想類型uid_t恰好是短整型。ISO C是決不允許這個例子的,因爲在老式風格中的參數子字被提升了。因此在這個例子中,函數定義的參數確實是個和原型參數的短整型類型不匹配的整型。
ISO C的這個限制是它難以寫出可以移植到傳統C編譯器上的代碼,因爲程序員不知道uit_t類型是短整型、整型還是長整型。因此, 像GNU C允許在這些情況下原型忽略新的老式風格的定義。更嚴謹的是在GNU C中,函數原型參數類型如果一個錢類型想後來的類型在提升以前一樣,則忽略更新的老式風格定義說明的參數。因此在GNU C中上面這些個例子等價與下面的例子:
  int isroot (uid_t); int isroot (uid_t x) { return x == 0; } 
GNU C++ 不支持老式風格函數定義,故這個擴展和它是不相關的。
5.29 C++風格註釋
在GNU C當中,你可以使用C++風格的註釋,就是一"//"開頭並且一直到本行末。許多其它的C實現方案允許這樣的註釋,並且它們可能成爲將來的C標準。但是,C++風格註釋在你說明了"-ansi",或"-std"選項來聲明使用ISO C在C99之前的版本時,將不會被識別,或者"-traditional"選項,因爲它們和傳統的被這樣的//*註釋*/分隔符分隔的結構不相容。
5.30 標識符名稱中的美元符
在GNU C當中,你可以一般的在標識符名稱中使用美元符。這是因爲許多傳統的C實現方案允許這樣的標識符。但是,標識符中的美元符在少量目標機器不被支持,典型原因是目標彙編器不允許它們。
5.31 常量中的ESC字符
你可以在一個字符串或字符常量中使用序列'/e'來表示ASCII字符ESC。
5.32 詢問變量對齊方式
關鍵字__alignof__允許你詢問一個對象如何對齊,或者一個類型的需要的最小對齊。它的語法很像sizeof。
例如,不過目標機器需要一個雙精度值來使一個8位的邊界對齊,這樣__alignof__(double)就是8.在許多RISC機器上就是這樣的。在很多傳統的機器設計,__alignof__(double)是4或者甚至是2.
一些機器實際上從不需要對齊;它們允許參考任意數據類型甚至在一個奇數地址上。對這些機器,__alignof__報告類型的建議對齊。
當__alignof__的操作數是一個左值而不是一個類型時,這個值是這個左值已知有的最大對齊。它可能由於它的數據類有而有這個對齊,或者因爲它是結構體的一部分並且從那個結構體繼承了對齊。例如在這個聲明之後:
struct foo { int x; char y; } foo1;
__alignof__(foo1.y)的值可能是2或4,同__alignof__(int)相同,即使foo1.y的數據類型自己不需要任何對齊。
這是要求一個不完全的類型的對齊的錯誤。
一個使你說明一個對象對齊的關聯特徵是__attribute__ ((aligned (alignment)));請看下節。
5.33說明變量屬性
關鍵字__attribute__允許你說明變量或結構體域的特殊屬性。這個關鍵字是跟有括在一對圓括號中的屬性說明。現在給變量定義了八個屬性:aligned, mode, nocommon, packed, section, transparent_union, unused,和weak。在特定的目標機器上定義了爲變量定義了一些其它屬性。其它屬性可以用於函數(見5.26節 聲明函數屬性)和類型(見5.34節指定類型屬性)。其它q前端和末端可能定義更多的屬性(見C++語言的擴展章節)。
你可能也說明屬性有‘__’開頭並且跟在每一個關鍵字後邊。這允許你在頭文件中使用它們而不必擔心可能有同名的宏。例如,你可以使用__aligned__來代替aligned。
見5.27節屬性語法,瞭解正確使用屬性語法的細節。
aligned (對齊) 
這個屬性以字節爲單位說明一個變量或結構體域的最小對齊。例如,這個聲明: 
int x __attribute__ ((aligned (16))) = 0; 
造成編譯器在一個16字節的邊界上分配全局變量x。在68040上,這可以用在和一個彙編表達式連接去訪問需要16字節對齊對象的move16指令。 
你也可以說明結構體域的對齊。例如,創建一個雙字對齊的整型對,你可以寫: 
struct foo { int x[2] __attribute__ ((aligned (8))); };
這是創建一個有兩個成員的共用體強制共用體雙字對齊的一個替換用法。 
它可能不能說明函數的;函數的對齊是被機器的需求所決定且不能被改變的。你不能說明一個typedef定義的名稱的對齊因爲這個名字僅僅是個別名,而不是特定的類型。 
在前邊的例子,你可以明確說明你希望編譯器以給定的變量或結構體域對齊(以字節位單位)。另外一種方案,你可以省略對齊因子並且進讓編譯器以你爲之編譯的目標機器的最大有用對齊來對齊變量或域。例如,你可以寫: 
short array[3] __attribute__ ((aligned)); 
無論何時你在一個要對齊的屬性說明中省略對齊因子,編譯器都會自動爲聲明的變量或域設置對齊到你爲之編譯的目標機器上對 曾經對任意數據類型用過的最大對齊。這樣做可以經常可以是複製動作更有效,因爲編譯器可以使用任何指令複製最大的內存塊當執行復制到你要求這樣對齊的變量 或域中或從這從中複製時。 
對齊屬性可以僅僅增加對齊;但是你可以通過也說明packed屬性。見下邊。 
注意這對齊屬性的可行性可能被你的連接器的固有限制所限制。在許多系統上,連接器僅僅可以把變量整理和對齊到一個特定的最大對齊。(對一些連接器,所支持的最大對齊可能非常非常小。)如果你的連接器幾近可以對齊變量最大8字節,那麼在__attribute__中說明aligned(16)仍然值提供給你8字節對齊。從你的連接器文檔中可以獲得更多的信息。 
mode (模式) 
這個屬性位聲明說明數據類型──任何類型都符合mode模式。這有效地使你獲取一個整型或浮點型的符合寬度。 
你可能也說明'byte'或'__byte__'模式來表示模式同一字節的整數一致,'word'或'__word'來表示一個字的整數的模式,並且'pointer'或'__pointer__'來表示指針的模式。 
nocommon
這個屬性說明要求GCC不要把放置一個變量爲 "common"而是直接給它分配空間。如果你說明'-fno-common'標誌,GCC將對所有變量這樣做。 給變量說明nocommon屬性則提供初值爲零。變量可能僅在一個源文件中初始化。 
packed 
packed屬性說明一個變量或結構體域應該有儘可能小的對齊──一個變量一字節或一個域一字節,除非你和對齊屬性一起說明了一個更大的值。 
這裏是一個域x被packed屬性說明的結構體,所以它直接跟在a之後: 
  struct foo { char a; int x[2] __attribute__ ((packed)); }; 
section("段名") 
通常,編譯器將把對象放置在生成它的段中想data和bss。但是有時候你學要附加段,或者你需要一定特定的變量去出現在特殊的段中,例如去映射特殊的硬件。section屬性說明一個變量(或函數)存在一個特定的段中。例如,這個小程序使用一些說明段名:   struct duart a __attribute__ ((section ("DUART_A"))) = { 0 }; struct duart b __attribute__ ((section ("DUART_B"))) = { 0 }; char stack[10000] __attribute__ ((section ("STACK"))) = { 0 }; int init_data __attribute__ ((section ("INITDATA"))) = 0; main() { /* Initialize stack pointer */ init_sp (stack + sizeof (stack)); /* Initialize initialized data */ memcpy (&init_data, &data, &edata - &data); /* Turn on the serial ports */ init_duart (&a); init_duart (&b); } 
和一個全局變量的初始化定義一起使用section屬性,就像例子中那樣。GCC給出一個警告或者在未初始的變量聲明中忽略section屬性。 
你可能由於連接器的工作方式僅同一個完全初始化全局定義使用section屬性。連接器要求每個對象被定義一次,例外的是未初始化變量假定竟如common(或bss)段而且可以多重“定義”。你可以強制一個變量帶'-fno-common'標誌初始化或nocommon屬性。 
一些文件格式不支持隨意的段,所以section屬性不全適用於所有的平臺。如果你需要映射一個完全滿足的模塊到一個特定的段,慎重考慮使用連接器的設備來代替。 
在Windows NT上, 在命名的段附加放置變量定義,這段也可以個在所有運行的可執行文件或DLL文件之間共享。例如,這個小程序通過將其放入命名的段並將該段標記位共享而定義了共享數據。   int foo __attribute__((section ("shared"), shared)) = 0; int main() { /* Read and write foo. All running copies see the same value. */ return 0; } 
你僅可以在section屬性完全初始化全局定義是使用shared屬性,因爲連接器的工作方式。看section屬性來獲得更多的信息。 
shared屬性僅適用於Windows NT。 
這個屬性附屬與一個共用體型的函數參數,意味着參數可能具有與共用體成員一致的任何類型,但是參數被當做共用的第一個成 員的類型傳送。看5.34節說明類型屬性瞭解更多細節。你也能吧這個屬性用在對共用體數據類型適用typedef是;這樣它就可以被用在所有這個類型的函 數參數上。 
unused 
變量有這個屬性,意味着這個變量可能沒有被使用。GCC將不對這個變量產生警告。 
weak 
weak屬性在5.26節聲明函數屬性已經被陳述過。 
model(模型名) 
在M32R/D上使用這個屬性去設置對象的編址能力。這個標識符模型名是small,medium或large中的一個,代表每一個代碼模型。 
小模型對象存在與低16MB內存中(所以它們的地址可以和一個ld24指令一起裝入)。 
中和大模型對象可能存在任何一個32位的地址空間中(編譯器將形成seth/add3指令來裝入它們的地址)。 
說明多個屬性用逗號吧它們分隔開寫在一對圓括號中:例如,'__attribute__ ((aligned (16), packed))'。
5.34 指定類型屬性
當你定義結構體和共用體類型時,關鍵字attribute允許你爲這些類型指定特殊的屬性。這個關鍵字後面跟隨着包含雙parentheses的指定類型。四中屬性常被定義爲:對齊(aligned),封裝(packed)型,透明共用體型(transparent-union)和未使用。另外的屬性則被定義爲函數(看5.26段函數屬性的聲明)和變量(看5.33段指定變量屬性)。
你可以指定這些屬性在關鍵字之前或後面。這就使你能在頭文件應用這種屬性而不必聲明 可能有同樣名字的宏 例如:你能用_aligned__ instead of aligned.
你可以在括號中放入枚舉的定義或聲明, 結構或共用類型的定義和集裝屬性,括號後指定其屬性。
你也能在枚舉,結構或共用間指定屬性的tag和名字而不是在)後。
看5.27 屬性語法,對於準確使用語法屬性
aligned 
這種屬性指定一個最小的隊列(按位算)爲變量指定類型。例如,如下的聲明: 
  struct S { short f[3]; } __attribute__ ((aligned (8))); typedef int more_aligned_int __attribute__ ((aligned (8))); 

強制使編譯器確定每個類型爲S的結構體變量或者更多的組合整型,將被分配和匹配爲至少8位。在可精簡效能結構中,當複製一個結構體S變量到另外一個時。擁有所有的結構體S 對齊8位的變量使編譯器能使用ldd和std,因此可以提高運行效率。 
{注意,任何給定的結構體或共同體類型的對齊是ISO C標準所需的,至少是正在談論的結構體或共同體類型所有成員對齊的最小公倍數的一個完全的倍數。這就意爲着你能有效的教正附屬於aligen對於結構體和共用體隊列成員的屬性。但是以上例子中插入的註釋更加明顯,直觀和可讀對於編譯者來校正一個完全的結構體或共用體類型組合。 
封裝(packed) 
這種屬性接近於枚舉,結構或者共用類型的定義,指定一個所需最小的內存用來代辦這種類型。 
爲結構體和共用體指定這種屬性等效於爲他們的每個成員指定集裝的的屬性。指定“短-枚舉”標誌等同於指定封裝的屬性在所有的枚舉定義。 
你也可以僅僅在括號後面指定這種屬性,而不是爲他定義類型的聲明,除非聲明也包括枚舉的定義。 
transparent_union 
這種屬性基於共用體的定義,表明一些函數的參數使用共用類型會被看作調用函數的一種特殊途徑.首先,參數與同透明共用體類型保持一致,能成爲任何類型的共用體;不需要轉換. 加入共用體包含指針類型,一致的參數能成爲一個空指針常量或空指針表達式;加入共用體包含空指針,一致參數能成爲指針表達式。加入共用體成員類型是之至, 類型修飾符就像指定,就如一般的指針的轉換一樣。 
第二,參數被傳給函數用到的調用轉換(透明共用體的第一個成員,不能調用轉換共用體本身。所有的成員必須擁有相同的機器代理;這就使參數傳輸能進行了。 
透明共用體擁有多種接口庫函數來處理兼容性問題。例如,假定等待函數必須要接受一種整型來遵從Posix,或者一個共用wait函數要適應4.1BSD接口。記入wait函數的參數是空型,wait函數將接受各種型的參數,但是它也能接受另外的指針型和這個將能使參數類型的檢測降低效用。而爲"sys/wait.h"定義的接口如下: 
  typedef union { int *__ip; union wait *__up; } wait_status_ptr_t __attribute__ ((__transparent_union__)); pid_t wait (wait_status_ptr_t); 

這種接口允許整形或共用體等待參數的傳輸,用整型的調用轉換,這個程序能調用參數和類型: 
  int w1 () { int w; return wait (&w); } int w2 () { union wait w; return wait (&w); } 

在這個接口下,wait的執行將是這樣: 
  pid_t wait (wait_status_ptr_t p) { return waitpid (-1, p.__ip, 0); } 

未用(unused) 
當應用一種類型(包括共用體和結構體),這個屬性意爲着這種變量類型可能出席那不能使用的,GCC講不能產生警告對這些類型的變量,幾十變量什麼作用都沒,這還經常能導致鎖或線程的種類(通常被定義但不引用,但是包含構建與消毀都存在非平凡簿記功能.) 
5.35 內聯函數像宏一樣快
通過聲明一個內聯函數,你就可以直接用GCC把函數源代碼和調用它的函數源代碼合成起來。這樣,通過消除高層的函數調用使得函數執行更快;另外,如 果任何的實參是常數,它們的已知值可能允許簡化從而不使所有的內聯函數代碼在編譯時被包含進來。這對代碼大小的影響幾乎是不可預知的;和內聯函數相比,結 果代碼的大或小取決於具體情況。函數內聯是一種優化,而且只在優化編譯上起作用。如果你沒有用'-0',那就沒有函數是真正內聯的。
在C99標準裏包含內聯函數,但是,當前,GCC的實現和C99的標準要求確實存在差異。
像這樣,在函數聲明裏用內聯的關鍵字可以聲明一個函數內聯:
  inline int inc (int *a) { (*a)++; } 
(如果你正在寫一個要被包含在一個標準C程序的頭文件,請用 __inline__ 代替 inline.參見5.39節 備用關鍵字。)你同樣可以通過加選項`-finline-functions'使得所有足夠簡單的程序內聯。
注意,在函數定義中的固定用法可以使函數不適合做內聯。這些用法有:varargs函數的使用 ,alloca函數的使用, 可變大小數據類型的使用(參見5.14節 變長數組),goto 計算的使用 (參見 5.3節 可賦值標籤), 非局部goto語句的使用, 以及嵌套函數的使用(參見 5.4節嵌套函數 ).用`-Winline'可以在一個函數被標記爲內聯而不能被取代時出現警告,並給出錯誤原因。
注意在C和Objective C中, 不象C++那樣, 內聯關鍵字不會影響函數的聯接。
GCC會自動將定義在C++程序內class body中的元函數內聯即使它們沒有被明確的聲明爲內聯。(你可以用`-fno-default-inline'忽略它;參見選項控制C++語法。)Options Controlling C++ Dialect
當一個函數同時是靜態和內聯時,如果所有對這個函數的調用都被綜合在調用者中,而且函數地址從沒有被使用過,函數所有的彙編代碼都沒有被引用過。在這種情況下,GCC事實上不會爲這個函數輸出彙編代碼,除非你指定選項`-fkeep-inline-functions'。由於各種原因一些函數調用不會被綜合(特殊的,調用在函數定義之前和在函數中的遞歸調用是不能被綜合的)。如果有一個非綜合調用,函數會被像平常一樣編譯出彙編代碼。如果程序引用了它的地址,這個函數也必須像平常一樣被編譯,因爲地址是不能被內聯的。
當一個函數不是靜態時,編譯器會假定在其它源文件中可能存在調用;由於在任何程序中全局符號(全局變量)只能被定義一次,函數一定不能在其它源文件中被定義,所以在那裏的調用是不能被綜合的。因此,通常一個非內聯函數總是被獨立的編譯。
如果你在函數定義中同時指定內聯和外部援引,那麼這個定義只會被用作內聯。即使沒有明確的引用它的地址,函數也決不會被獨立編譯。這些地址變成了外部援引,就好像你只是聲明瞭函數,沒有定義它一樣。
這種內聯和外部援引的結合和宏定義的效果差不多。這種結合的用法是把函數定義和這些關鍵字放在一個頭文件中,把另外一份定義(缺少內聯和外部援引) 的拷貝放在庫文件中。頭文件中的定義會使對這個函數的大多數調用成爲內聯。如果還有其它函數要用,它們將會查閱庫文件中專門的拷貝文件。
爲了將來當 GCC 實現 C99 標準語法中的內聯函數時有兼容性,僅使用靜態內聯是最好的。(當`-std=gnu89'被指明時,當前語法可以保留可用部分,但最後的默認將會是`-std=gnu99',並且它將會實現C99語法,儘管現在他並沒有被這樣做。)
GCC沒有優化是沒有內聯任何函數。內聯好還是不好並不明確,既然這樣,但是我們發現沒有優化時正確執行是很困難的。所以我們做簡單的事,避開它。
5.36 彙編指令和C表達式 操作數
在彙編指令中用彙編語言,你可以指定用C語言中的表達式的操作數。這就意味着你不需要猜測哪個寄存器或存儲單元中包含你想要用的數據。
你必須指定一個儘可能像機器描述中的彙編指令模塊,爲每個操作數加上一個被約束排成一列的操作數。
這是怎樣使用68881的 fsinx指令的例子:
asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
這裏angle是一個用來輸入操作數的C表達式,result是輸出操作數。每個都有`"f"'作爲它的操作數約束,說明需要一個浮點寄存器。`=f' 中的`='指明瞭這個操作數是一個輸出;所有輸出操作數的被約束使用`='。這種約束在同語言中被用於機器描述(參見20.7節 操作數約束)。
每個操作數在插入語中被一個後面跟着C表達式的約束操作數字符串描述。一個冒號隔開了彙編程序模塊和第一個輸出操作數,另一個隔開了最後一個輸出操 作數和第一個輸入操作數,即便要的話。逗號在每個羣中隔開了不同的操作數。操作數的總數被限制在10或者被限制在操作數的最大數字,在任何機器描述中的任 何指令模型,無論哪一個都較大。
如果只有輸入操作數而沒有輸出操作數,你應該在輸出操作數在的位置的兩頭放兩個連續的冒號。
輸出操作數表達式必須是一個左值;編譯器可以檢測這點。輸入操作數不需要是左值。編譯器不能檢測操作數的數據類型對指令執行來說是否合理。它不能解 析彙編指令模塊,它也不知道彙編指令的意思。甚至不能判斷它是否是一個有效的彙編輸入。擴展彙編的特徵是多數情況下用於機器指令,而編譯器本身卻不知道它 的存在。如果輸出表達式不可能是直接地址(比如說,它是一個位域),你的約束必須允許一個寄存器。那樣的話,GCC將會把寄存器當作彙編程序的輸出,接下 來存儲寄存器內容用作輸出。
普通的輸出操作數必須是隻讀的;GCC 會假定這些在指令之前操作數中的左值是死的,並且不需要產生。擴展彙編支持輸入-輸出或讀-寫操作數。用字符`+'可以指出這種操作數並且在輸出操作數中列出。
當對一個讀寫操作數(或是操作數中只有幾位可以被改變)的約束允許一個而中選一的寄存器,你可以從邏輯上把它的作用分成兩個操作數,一個是輸出操作 數和一個只寫輸出操作數。他們之間的關係是在指令執行時,被約束表示出他們需要在同一個位置。你可以對兩個操作數用同樣的C語言表示或不同的表示。這裏我 們寫了一個結合指令(假想的)用後備地址寄存器當作它的只讀資源操作數,把foo作爲它的讀寫目的地。
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
對操作數1來說,`"0"'約束是指它必須佔據相同的位置相操作數0一樣。在約束中一個阿拉伯數字只被允許出現在輸入操作數中,而且,它必須提及到一個輸出操作數。
在約束中只有一個阿拉伯數字可以保證一個操作數會和其它操作數一樣出現在同一個地方。起碼的事實,foo是兩個操作數的確切值並不足以保證在產生的彙編代碼中它們會出現在相同的位置。下面的代碼是不可靠的:
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
各種優化或重新裝載可以使操作數0和1在不同的寄存器中;GCC 知道沒有理由不這樣做。舉例來說,編譯器可能會找到一個寄存器中foo值得拷貝並用它作爲操作數1,但是產生的輸出操作數0卻在另外一個寄存器中(後來拷 貝到foo自己的地址裏)。當然,由於用於操作數1的寄存器在彙編碼中甚至沒有被提及,就不會產生結果。但GCC卻不能指出來。
一些頻繁使用的指令指定了硬設備寄存器。爲了描述這些,在輸入操作數之後寫上第三個冒號,後面緊跟着頻繁使用的硬設備寄存器的名字(以串的形式給出)。這又一個現實的例子:asm volatile ("movc3 %0,%1,%2" : /* no outputs */ : "g" (from), "g" (to), "g" (count) : "r0", "r1", "r2", "r3", "r4", "r5"); 

你不可能通過用一個輸入操作數或一個輸出操作數交迭的方式來描述一個頻繁使用的硬設備寄存器。舉個例子,如果你在快表中提到一個寄存器,你就不可能 用一個操作數描述一個有一個成員的寄存器組。你沒有辦法去指定一個輸入操作數沒有同時指定爲輸出操作數時被修正。注意如果你指定所有輸出操作數都出於這個 目的(而且因此沒有被使用),你就需要去指定可變的彙編代碼構造,像下面說得那樣,去阻止GCC刪除那些沒有被用到的彙編代碼段。
如果你從彙編代碼中找到一個特殊的寄存器,你大概不得不在第三個冒號列出之後這個寄存器來告訴編譯器寄存器的值是修正值。在一些彙編程序中,寄存器的名字以`%'開始;要在彙編代碼中生成一個‘%’,你必須在輸入時這樣寫:`%%'。
如果你的彙編指令可以改變條件碼寄存器,在頻繁使用的寄存器列表中加上`cc'。GCC在一些機器上表現條件碼像制定硬設備寄存器一樣;`cc'可以去命名這種寄存器。在其它機器上。條件碼被給於不同的操作,而且指定`cc'沒有效果。但是它在任何機器上總是有效的。
如果你的彙編指令通過不可預知的方式修正存儲器,在頻繁使用的寄存器列表中加上`memory'。這會使GCC通過彙編指令不把存儲器的值存入寄存器。如果存儲器沒有受影響,你也可以加上可變的沒有被列在彙編程序輸入輸出表上的關鍵字,就像`memory'的頻繁使用沒有被計算反而成爲彙編程序的副作用一樣。
你可以在一個彙編模塊中把多個彙編指令放在一起,用系統中通常用的字符將它們隔開。在大多數地方一個聯合是新的一行打斷原來的行,加上一個空格鍵移 動到指令區域(象這樣寫 /n/t)。如果彙編程序允許將逗號作爲斷行字符的話,逗號是可以使用的。注意,一些彙編程序語法將逗號作爲註釋的開始。輸入操作數被保證不被用於任何頻 繁使用的寄存器和輸出操作數的地址,所以你可以隨意讀寫頻繁使用的寄存器。以下是一個模塊中多重指令的例子;它假定子程序 _foo從寄存器9和10上接受參數:
  asm ("movl %0,r9/n/tmovl %1,r10/n/tcall _foo" : /* no outputs */ : "g" (from), "g" (to) : "r9", "r10"); 
除非一個輸出操作數有`&'修正限制,否則GCC會把它當作一個不相干的操作數而分配給它一個相同的寄存器,而這是建立在輸出產生之前輸入 被銷燬的假定之上。當彙編代碼多於一條指令時,這個假定就可能有錯。這種情況下,對每個輸出操作數用`&'可能不會和一個輸入交迭。參見 20.7.4修正限制字符。
如果你想測試一下由彙編指令產生的代碼情況,你必須包含一個象下面這樣的分支和標誌和彙編構造:
  asm ("clr %0/n/tfrob %1/n/tbeq 0f/n/tmov #1,%0/n0:" : "g" (result) : "g" (input)); 
這個要假定你的彙編程序支持局部標籤,象GNU彙編器和大多數UNIX彙編器做的那樣。
談到標籤,不允許從一個彙編程序跳到另一個,編譯器優化者不知道這樣的跳轉,所以當它們決定怎樣去優化時不會考慮這些。
用匯編指令通常最方便的方法是把指令壓縮在一個象宏定義的函數裏。舉個例子:
  #define sin(x) ({ double __value, __arg = (x); asm ("fsinx %1,%0": "=f" (__value): "f" (__arg)); __value; }) 
這裏用可變的__arg來保證指令在合適的double型特徵值上運行,而且僅僅接受這些能自動轉換爲double的參數。
另外一個可以確保指令在正確的數據類型上運行的方法是在彙編程序中用一張表。這和用可變__arg指出在於它可以轉化爲更多的類型。舉例來說:你想得到的類型是整型,整形參數會接受一個指針而不會出錯,當你把這個參數傳給一個名字爲__arg的整型變量時,就會出現使用指針的警告,除非你再調用函數中明確轉化它。
如果一個彙編程序已經有了輸出操作數,爲了優化GCC會假定指令對改變輸出操作數沒有副作用。這並不意味着有副作用的指令不會被用到,但你必須要小 心,編譯器會略去沒有被使用的輸出操作數或使他們移出循環,如果他們組成一個表達式時,會用一個代替兩個。同時當你的指令在一個看上去不會改變的變量上沒 有副作用,如果它被在一個寄存器中找到的話,舊的值在不久之後會被再次使用。
通過在彙編程序後寫上可變關鍵字,你可以阻止一條彙編指令被刪除,永久被移走,或被結合起來。例:
  #define get_and_set_priority(new) ({ int __old; asm volatile ("get_and_set_priority %0, %1" : "=g" (__old) : "g" (new)); __old; }) 
如果你寫的彙編指令沒有輸出,GCC會知道這條指令有副作用而不會刪除它或者把它移到循環外。
可變關鍵字表示指令有很大的副作用。GCC不會刪除一段彙編程序如果它是可達的話。(指令仍會被刪除如果GCC不能證明控制流會到達指令。)GCC對一些可變的彙編指令不會重新排序。例:
  *(volatile int *)addr = foo; asm volatile ("eieio" : : ); 
假定addr包含一個映射到設備寄存器的內存地址。個人微機的eieio(強迫輸入輸出執行順序)指令會告訴CPU要保證在其它輸入輸出之前要執行設備寄存器中所保存的指令。
注意對編譯器來說即使是一個看上去無關緊要的可變彙編指令也能被移動,比如通過jump指令。你不要期盼一個可變彙編指令的順序會保持非常的連貫。 如果你需要連貫的輸出,就用單一的彙編程序吧。GCC還會通過一條可變彙編指令來進行一些優化;GCC不會像其它編譯器一樣在遇到可變彙編指令時忘記每一 件事。
沒有任何操作數的彙編指令會像一個可變彙編指令來同等對待。
准許訪問彙編程序指令條件碼是很自然的想法。然而,但我們試圖去實現這個時,卻沒有可靠的辦法。問題在於輸出操作數可能會被再次裝載,這可能會導致 額外增加的儲存指令。在大多數機器上,這些指令會在測試時間改變條件碼。這些問題不會出現在普通的測試和比較指令上。因爲他們沒有任何輸出操作數。
由於和上面提到的相似的原因,讓一個彙編指令有權訪問先前指令留下的條件碼是不可能的。如果你要寫一個被包含在標準C程序中的頭文件時,用__asm__ 代替asm。參見5.39 備用關鍵字
5.36.1 i386浮點數彙編操作數
在彙編操作數使用規則中有很多是站寄存器的用法。這些規則只對棧寄存器中的操作數起作用:
在彙編程序中給一組死的操作數,就需要知道那些在彙編時被暗自取出,而在GCC中必須是被明確彈出的。一個在彙編時會被暗自彈出的輸入寄存器必須被明確被標誌爲頻繁使用的,除非你強制它去匹配一個輸出操作數。
對任何在彙編時會被暗自彈出的輸入寄存器,就需要知道怎樣去調解一個堆棧進行彈出補償。如果棧裏任何沒有彈出的輸入比任何彈出寄 存器更靠近棧頂,就很難知道棧是什麼樣子,剩下的棧的去向也就很難知道了。任何會被彈出的操作數都要比不被彈出的操作數更靠近棧頂。如果一個輸入死在an insn,對輸出重新裝載可能會用到輸入寄存器。看看下面這個例子:
asm ("foo" : "=t" (a) : "f" (b));
這行彙編程序的意思是輸入的B不會在彙編時被彈出,彙編器會把結果壓入棧寄存器,棧會比壓入之前更深一層。但是,如果B死在這insn裏,重新裝載對輸入和輸出用同一個寄存器是有可能的。
如果任何輸入操作數用到了f約束, 所有的輸出寄存器限制必須用到& earlyclobber。上面的彙編程序可以寫成這樣: asm ("foo" : "=&t" (a) : "f" (b));
一些操作數需要放在棧的特殊位置。
由於所有的387操作碼用到了讀寫操作數,在彙編操作數之前的輸出操作數都是死的,而且會被彙編操作數壓棧。除了棧頂之外壓在任何地方是沒有意義的。
輸出操作數必須從棧頂開始:而且不可能是一個寄存器跳躍。
一些彙編程序段可能需要額外的棧空間用於內部計算。這可以用式輸入輸出與棧寄存器不相干的方法來保證。 這裏有幾個要去寫的彙編代碼。這段彙編接收一個可以在內部彈出的輸入,併產生兩個輸出。
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");這段彙編接收兩個通過操作碼fyl2xp1彈出的輸入,併產生一個輸出代替它們。用戶必須給出用於棧寄存器頻繁使用的st(1)的C代碼來了解fyl2xp1 彈出兩個輸入。
5.37彙編代碼中使用的控制名字
在進行聲明之後你可以爲C函數或函數變元的彙編代碼中寫入asm (or __asm__)來指定你要在彙編代碼中使用的名字,象下面這樣:
int foo asm ("myfoo") = 2;
這種用於在彙編代碼中用於變量foo指定應該是myfoo' 而不是通常的 `_foo'.
系統中的C函數或變量通常是以下劃線開始的,這種特徵就不允許你以下劃線開始爲一個連接命名。
非靜態局部變量對這種特徵是沒有意義的,因爲這種變量不需要由彙編程序名。如果你試着把一個變量放在一個指定的寄存器中,看看5.38節 指定寄存器中的變量。GCC目前把這種代碼當作一種警告, 但是在不久後它就可能變成一個錯誤,而不僅僅是警告了。
你不能在函數定義時這樣用匯編;但是你可以像這樣,在定義前爲函數寫一個聲明並且把彙編代碼放在這裏:
  extern func () asm ("FUNC"); func (x, y) int x, y; ... 
確保彙編程序的名字不會與其它任何彙編標記發生衝突。而且,不能使用寄存器名;那會產生完全無效的彙編代碼。GCC目前還沒有能力在寄存器中存儲這些靜態變量,以後或許會被架上。
5.38 指定寄存器中的變量
GNU C允許你把商量的全局變量放在指定的硬設備寄存器中。你還可以在爲普通變量分配的寄存器中指定這樣的寄存器。 全局寄存器變量通過程序保留寄存器。這在編程中非常有用,比如有多個會被頻繁訪問全局變量的編程語言解釋程序。 指定寄存器中的局部寄存器變量不需要保有寄存器。編譯器的數據流分析有能力決定那裏指定的寄存器包含活的值,這些變量在哪裏會被其它代碼用到。在數據流分析中表現爲死的寄存器變量可能會被刪除。提到寄存器變量可能會被刪除、移動或是簡化。
通過擴展彙編的特徵局部變量有時使用起來很方便(參見5.36 彙編指令和C表達式 操作數),如果你要用匯編指令直接在指定寄存器中寫入輸出值的話。(倘若你指定的寄存器適合彙編裏操作數的指定限制的話)。
5.38.1 定義全局寄存器變量
GCC中你可以這樣定義全局寄存器變量:
register int *foo asm ("a5");
這裏的a5是要使用的寄存器名。在你的機器上選一個通常被保留或是被函數調用釋放的寄存器,以使庫函數不能頻繁使用它。
通常來說寄存器名是由CPU決定的,所以你可能需要根據CPU類型調整你的程序。在68000上寄存器a5是一個好的選擇。在有寄存器窗口的機器上,一定要選擇一個不會受函數調用機制影響的全局變量寄存器。
另外,運行在同種CPU上的操作系統可能會在寄存器命名上有區別;這樣你就需要更多的條件。舉例來說,一些68000操作系統把這個寄存器叫做%a5。
最終有了一個可以讓編譯器自動選擇寄存器的方法,但首先我們要決定編譯器怎樣去選並讓你去引導這個選擇。顯然沒有這種辦法。
在特定寄存器上定義一個全局寄存器變量,要保證寄存器完全用作這個用途,至少在當前時這樣實現的。這些寄存器不會被函數存儲起來。存儲這些寄存器永遠不會被刪除即使它表現爲死,但是提到寄存器變量可能會被刪除、移動或是簡化。 從信號或多個控制線程操作這些全局變量是不安全的,因爲系統庫函數可能會爲其它事用到這些寄存器(除非你爲了手頭的工作特別重新編譯它們)。
一個函數通過第三個不知道這個全局變量的函數(也就是說,在不同的源文件中這個變量沒有被聲明),用這個全局變量去調用另一個foo這樣的函數是不安全的。這是由於損失可能保有的這個寄存器並把其它變量放在那裏。舉例來說,你不能指望一個你要傳遞給qsort的全局變量在比較函數中被用到,因爲qsort可能會把其它的什麼東西放在那個寄存器中。(如果你用相同的全局變量重新編譯qsort,問題就會得到解決)
如果你要重新編譯qsort或是世上沒有用到你的全局寄存器變量的源文件,已達到它們不使用那個寄存器的目的,接下來它就有能力指定編譯器選項`-ffixed-reg'。事實上你不需要爲那些源文件增加一個全局變量寄存器聲明。
編譯時沒有這個全局寄存器變量的函數去調用一個可以改變全局寄存器變量的函數是安全的,因爲它可以經常使調用者期待在那裏找到用來返回的值。因此,用到全局變量的程序的入口函數必須被明確保存,並且要恢復屬於調用者的值。
在大多數機器上,在設置跳躍的時候,遠跳躍會恢復每一個全局寄存器變量的值。而在另一些機器上,遠跳躍不會改變這些值。考慮到移植性,調用設置跳躍指令的函數必須用其它參數儲存這些全局寄存器變量的值,並在遠跳躍時恢復他們。這樣,無論遠跳躍做什麼都有同樣的效果。
所有的全局寄存器變量聲明必須在函數定義之前。否則,那些寄存器可能在先前的函數中被使用,而聲名阻止這樣卻晚了。
全局寄存器變量不應該有初始值,因爲一個可執行文件並不意味着會爲寄存器提供初始值。
在sun工作站上,g3 ... g7被認爲是合適的寄存器,但是某幾個庫函數,像getwd,用於分割和求餘的子程序,修正了g3 和 g4,g1 和 g2 是局部臨時存儲器。
在68000上,a2 ... a5 d2 ... d7應該是合適的,當然,用很多這樣的寄存器就不合適了。
5.38.2 用於局部變量的指定寄存器
你可以用一個制定的寄存器定義一個局部寄存器變量:
register int *foo asm ("a5");
這裏的a5是要使用的寄存器名。注意這和定義全局寄存器變量的語法是相同的,只是局部寄存器變量要出現在一個函數中。
通常來說寄存器名是由CPU決定的,但這並不是問題,因爲指定寄存器大多用在外在的彙編指令上(參見5.36 彙編指令和C表達式 操作數)。所有這些通常都需要你根據CPU的類型給你的程序加上條件。
另外,運行在同種CPU上的操作系統可能會在寄存器命名上有區別;這樣你就需要更多的條件。舉例來說,一些68000操作系統把這個寄存器叫做%a5。
定義這樣一個局部寄存器變量,不需要保有這個寄存器;它留下的其它代碼可用的部分放在控制流可以決定變量的值不是活着的地方。然而,這些寄存器在重新裝載途徑是不可用的;過多地使用這些特徵會使編譯器在編譯特定程序時沒有可用的寄存器。
不是任何時候這些選項都能保證GCC能夠產生寄存器中包含這種變量的代碼。在一個彙編聲明中你不必要爲這個寄存器寫一段外在的說明並假定它總是會提及這個變量。 當變量的值在數據流分析中表現爲死的時候,存儲在變量中的值可能會被刪除。提到寄存器變量可能會被刪除、移動或是簡化。
5.39 備用關鍵字
用選項`-traditional'可以使一些關鍵字失去作用;`-ansi' 和各種 `-std'選項是另外一些失效。在一個被所有程序(指包含標C和傳統C)時都要使用的多種功能的頭文件中,當你要用GNU擴展C或標準C時,就會產生麻煩。在編譯程序是加上選項`-ansi'時,關鍵字asm, typeof 和 inline會失效(儘管inline可以和`-std=c99'用在程序編譯中),同時在編譯程序是加上選項`-traditional'時,關鍵字const, volatile, signed, typeof 和 inline會失效。標準C99裏限制的關鍵字只有當`-std=gnu99'(被最終默認)或者是`-std=c99'(等價於 `-std=iso9899:1999') 被使用時纔會有效。
解決這個問題的方法是在每個有爭議的關鍵字的首部和尾部加上`__'。例如:用 __asm__ 代替 asm, __const__ 代替 const, __inline__ 代替 inline.
其它編譯器不會接受這種有選擇性的關鍵字;
如果你要有其它的編譯器編譯,你可以通過宏用習慣的關鍵字去替代備用的關鍵字。像這樣:
  #ifndef __GNUC__ #define __asm__ asm #endif 
`-pedantic'和其它的一些選項對許多GNU C擴展會產生警告。你可以通過在一個表達式前寫上__extension__來阻止這種警告。這樣用__extension__ 不會有副作用。
5.40 不完整的枚舉類型
你可以不用指定它的可能值定義一個枚舉標記。這樣會產生一個不完整的類型,很像你寫了一個結構體foo而沒有描述它的元素所得到的東西。一個稍後的聲明可以指定可能的值來完善這個類型。
你不能給不完善的類型分配存儲空間,或使它成爲變量。然而,你可以用指針指向它。
這個擴展或許不是非常有用,但它是枚舉類型的操作和對結構體和共用體的操作更加一致。 GNU C++不支持這個擴展。
5.41 用字符串命名函數
GCC預先假定兩個魔術標識符用來保存當前函數名。當函數名出現在源文件中時,函數名會被保存在標識符__FUNCTION__中。標識符__PRETTY_FUNCTION__保存着在一個語言的特殊模式中打印出來很漂亮的函數名字。
  extern "C" { extern int printf (char *, ...); } class a { public: sub (int i) { printf ("__FUNCTION__ = %s/n", __FUNCTION__); printf ("__PRETTY_FUNCTION__ = %s/n", __PRETTY_FUNCTION__); } }; int main (void) { a ax; ax.sub (0); return 0; } 
輸出如下:
  __FUN CTION__ = sub __PRETTY_FUNCTION__ = int a::sub (int) 
 

編譯器會自動用文字傳中含有合適名字的字符串替代標誌服。這樣,他們就不會預處理宏和變量,如__FILE__ 和 __LINE__。這就意味着它們和其它的字符串是由連接關係的,而且它們可以用來初始化字符數組。比如:
  char here[] = "Function " __FUNCTION__ " in " __FILE__; 
另一方面,`#ifdef __FUNCTION__' 在函數中沒有任何特殊的意義,因爲預編譯程序不會對標識符__FUNCTION__做任何專門的事。
GCC仍然支持C99標準裏定義的魔術字__func__。
標誌符被編譯者含蓄的聲明瞭就好像發表聲明,立即執行每個函數定義的下一個操作句柄,函數名是裝入詞彙表函數。這個名字是原始的函數名。
static const char __func__[] = "function-name";
這樣定義時,__func__是一個變量而不是一個字符串。特別指出,__func__不能連接其它字符串。
C++中,和聲明__func__一樣,__func__和__PRETTY_func__都是變量。
5.42 獲取函數返回值或結構地址 
這些函數可以被用來獲取函數中調用者的信息。
內置函數: void * __builtin_return_address (unsigned int level)
這個函數返回了當前函數的返回地址,或是它的主調函數地址。這裏的等級參數是一個用於掃描調用棧的結構數字。如果這個值是0的話,返回的是當前函數 返回地址;如果是1的話,返回的是當前函數的主調函數的返回地址,等等。等級參數必須是一個整型常量。在一些機器上,不可能決定除了當前函數外其它函數的 返回地址。在這種情況下,或者是沒有到達棧頂,這個函數會返回0。
這個函數只能和非零參數一起用作調試。
內置函數: void * __builtin_frame_address (unsigned int level)
這個函數和__builtin_return_address很相似,但他返回的是函數結構而不是函數地址。用0值調用這個函數時返回當前函數的結構地址,用1值調用這個函數時返回當前函數的主調函數的結構地址,等等。
這裏的結構是堆棧中保存局部變量和寄存器的一塊區域。結構地址通常是第一個被這個函數壓入堆棧的第一個字的地址。然而,精確的定義取決於處理器和調用協定。如果處理器有一個專門的結構指針寄存器,並且函數有一個這樣的結構,函數會返回這個結構指針寄存器的值。用於函數__builtin_return_address中的警告同樣適用於這個函數。
5.43 GCC提供的其它內置函數
GCC提供了大量的內置函數。其中一些只在處理例外或變量長度參數表時內部使用。之所以沒有列出,是由於它們可能隨時間變化;一般情況下,我們不推薦使用這些函數。
保留的函數是爲優化而提供的。
GCC包含了許多標準C庫函數的內置版本。加了前綴的版本會被當作C庫函數即使你加了`-fno-builtin'選項。(參見3.4 C語法控制選項)。許多這樣的函數只有在特定情況下才會被優化;如果在某種特例下沒有被優化,這個函數就會去調用庫函數。
這些函數會被識別並假定沒有返回abort, exit, _Exit 和 _exit,但裏一方面卻不是內置的。在嚴格的國際標準C模式下(使用了`-ansi', `-std=c89' 或 `-std=c99')沒有_exit。在嚴格的C89模式下(使用了`-ansi' 或 `-std=c89')沒有_Exit。
在嚴格的ISO C模式外,函數alloca, bcmp, bzero, index, rindex 和 ffs被當作內置函數處理。相應的版本__builtin_alloca, __builtin_bcmp, __builtin_bzero, __builtin_index, __builtin_rindex 和 __builtin_ffs 會在嚴格的國際標準C模式下被識別。
ISO C99函數conj, conjf, conjl, creal, crealf, creall, cimag, cimagf, cimagl, llabs 和 imaxabs 除了在嚴格的C89模式下都被當作內置函數。在國際標準C99模式下的函數cosf, cosl, fabsf, fabsl, sinf, sinl, sqrtf, 和 sqrtl的內置版本在任何模式都可被識別,這是由於爲了在標準C99下提出使用它們,C89保留了這些函數名字。所有這些函數的相應版本前都加了__builtin_前綴。
除非指定選項`-fno-builtin',否則這些C89中的函數會被當作內置函數加以識別:abs, cos, fabs, fprintf, fputs, labs, memcmp, memcpy, memset, printf, sin, sqrt, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, 和 strstr。所有這些函數的相應版本前都加了__builtin_前綴。
GCC提供了國際C99標準的浮點數比較宏的版本(可以避免提高無序操作數的例外):__builtin_isgreater, __builtin_isgreaterequal, __builtin_isless, __builtin_islessequal, __builtin_islessgreater, 和 __builtin_isunordered。
內置函數:int __builtin_constant_p (exp)
你可以用內置函數__builtin_constant_p來決定在編譯時是否一個值是常數,以此GCC可以執行含有那個常數的常量疊加表達式。這個函數的參數是要測試的值。如果那個值在編譯時是常量,函數返回整數1,否則返回0。返回0並不表示那個值不是常數,不過通過給定`-O'選項,GCC不能證明它是一個常量。
這個函數的一個有代表性的用途是內存成爲臨界資源時的深入應用。假設你有許多複雜的運算,如果這裏麪包含常數的話,你可能需要它被打包。沒有的話,就需要去調用一個函數。例如:
  #define Scale_Value(X) (__builtin_constant_p (X) ? ((X) * SCALE + OFFSET) : Scale (X)) 
你可以在一個宏或是內聯函數中使用這個內置函數。然而,如果你在一個內聯函數中用到它並把這個函數的參數傳給內置函數,當你通過一個字符常量或複合字(參見5.21 複合文字)調用這個內聯函數時,GCC不會返回1;除非你指定選項`-O' ,否則當你給內聯函數傳遞一個數字常量時,GCC不會返回1。
你還可以用__builtin_constant_p初始化靜態數據。例如:
  static const int table[] = { __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1, /* ... */ }; 
即使EXPRESSION不是一個常量表達式,這樣的初始化也是可以接受的。在這種情況下,GCC應該在評估內置函數時更加保守,因爲這沒有機會去優化。
先前的GCC版本不接受這樣的數據初始化內置函數。最早的完全可以支持的版本是3.0.1。
內置函數:long __builtin_expect (long exp, long c)
使用__builtin_expect 是爲編譯器提供分支預測信息。總的來說,你應該更喜歡真實地反饋(通過`-fprofile-arcs'),因爲程序員在預言他們的程序執行時聲名不怎麼好。然而,在有些應用程序中收集這樣的信息是很難的。
返回值是exp的值,exp應該是一個整型表達式。c的值在編譯時必須是一個常數。內置函數的語法期望exp == c.例如:
  if (__builtin_expect (x, 0)) foo (); 
必須指出,我們並不指望調用foo,因爲我們希望x的值是0。限於exp必須是整形表達式,當測試指針或浮點時應使用這樣的結構:
  if (__builtin_expect (ptr != NULL, 1)) error (); 

發佈了6 篇原創文章 · 獲贊 10 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章