在閱讀GNU/Linux內(nèi)核代碼時(shí),我們會遇到一種特殊的結(jié)構(gòu)初始化方式。該方式是某些C教材(如譚二版、K&R二版)中沒有介紹過的。這種方式稱為指定初始化(designated initializer)。下面我們看一個(gè)例子,
Linux-2.6.x/drivers/usb/storage/usb.c中有這樣一個(gè)結(jié)構(gòu)體初始化項(xiàng)目:
static struct usb_driver usb_storage_driver = {
.owner = THIS_MODULE,
.name = "usb-storage",
.probe = storage_probe,
.disconnect = storage_disconnect,
.id_table = storage_usb_ids, };
乍一看,這與我們之前學(xué)過的結(jié)構(gòu)體初始化差距甚遠(yuǎn)。其實(shí)這就是前面所說的指定初始化在Linux設(shè)備驅(qū)動程序中的一個(gè)應(yīng)用,它源自ISO C99標(biāo)準(zhǔn)。以下我摘錄了C Primer Plus第五版中相關(guān)章節(jié)的內(nèi)容,從而就可以很好的理解2.6版內(nèi)核采用這種方式的優(yōu)勢就在于由此初始化不必嚴(yán)格按照定義時(shí)的順序。這帶來了極大的靈活性,其更大的益處還有待大家在開發(fā)中結(jié)合自身的應(yīng)用慢慢體會。
已知一個(gè)結(jié)構(gòu),定義如下
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value; };
C99支持結(jié)構(gòu)的指定初始化項(xiàng)目,其語法與數(shù)組的指定初始化項(xiàng)目近似。只是,結(jié)構(gòu)的指定初始化項(xiàng)目使用點(diǎn)運(yùn)算符和成員名(而不是方括號和索引值)來標(biāo)識具體的元素。例如,只初始化book結(jié)構(gòu)的成員value,可以這樣做:
struct book surprise = {
.value = 10.99 };
可以按照任意的順序使用指定初始化項(xiàng)目:
struct book gift = { .value = 25.99,
.author = "James Broadfool",
.title = "Rue for the Toad"};
正像數(shù)組一樣,跟在一個(gè)指定初始化項(xiàng)目之后的常規(guī)初始化項(xiàng)目為跟在指定成員后的成員提供了初始值。另外,對特定成員的最后一次賦值是它實(shí)際獲得的值。例如,考慮下列聲明:
struct book gift = { .value = 18.90,
.author = "Philionna pestle",
0.25};
這將把值0.25賦給成員value,因?yàn)樗诮Y(jié)構(gòu)聲明中緊跟在author成員之后。新的值0.25代替了早先的賦值18.90。 有關(guān)designated initializer的進(jìn)一步信息可以參考c99標(biāo)準(zhǔn)的6.7.8節(jié)Ininialization。
特定的初始化標(biāo)準(zhǔn)C89需要初始化語句的元素以固定的順序出現(xiàn),和被初始化的數(shù)組或結(jié)構(gòu)體中的元素順序一樣。
在ISO C99中,你可以按任何順序給出這些元素,指明它們對應(yīng)的數(shù)組的下標(biāo)或結(jié)構(gòu)體的成員名,并且GNU C也把這作為C89模式下的一個(gè)擴(kuò)展。這個(gè)擴(kuò)展沒有在GNU C++中實(shí)現(xiàn)。
為了指定一個(gè)數(shù)組下標(biāo),在元素值的前面寫上“[index] =”。比如:
int a[6] = { [4] = 29, [2] = 15 };
相當(dāng)于:
int a[6] = { 0, 0, 15, 0, 29, 0 };
下標(biāo)值必須是常量表達(dá)式,即使被初始化的數(shù)組是自動的。
一個(gè)可替代這的語法是在元素值前面寫上“.[index]”,沒有“=”,但從GCC 2.5開始就不再被使用,但GCC仍然接受。 為了把一系列的元素初始化為相同的值,寫為“[first ... last] = value”。這是一個(gè)GNU擴(kuò)展。比如:
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
如果其中的值有副作用,這個(gè)副作用將只發(fā)生一次,而不是范圍內(nèi)的每次初始化一次。
注意,數(shù)組的長度是指定的最大值加一。
在結(jié)構(gòu)體的初始化語句中,在元素值的前面用“.fieldname = ”指定要初始化的成員名。例如,給定下面的結(jié)構(gòu)體,
struct point { int x, y; };
和下面的初始化,
struct point p = { .y = yvalue, .x = xvalue };
等價(jià)于:
struct point p = { xvalue, yvalue };
另一有相同含義的語法是“.fieldname:”,不過從GCC 2.5開始廢除了,就像這里所示:
struct point p = { y: yvalue, x: xvalue };
“[index]”或“.fieldname”就是指示符。在初始化共同體時(shí),你也可以使用一個(gè)指示符(或不再使用的冒號語法),來指定共同體的哪個(gè)元素應(yīng)該使用。比如:
union foo { int i; double d; };
union foo f = { .d = 4 };
將會使用第二個(gè)元素把4轉(zhuǎn)換成一個(gè)double類型來在共同體存放。相反,把4轉(zhuǎn)換成union foo類型將會把它作為整數(shù)i存入共同體,既然它是一個(gè)整數(shù)。(參考5.24節(jié)向共同體類型轉(zhuǎn)換。)
你可以把這種命名元素的技術(shù)和連續(xù)元素的普通C初始化結(jié)合起來。每個(gè)沒有指示符的初始化元素應(yīng)用于數(shù)組或結(jié)構(gòu)體中的下一個(gè)連續(xù)的元素。比如,
int a[6] = { [1] = v1, v2, [4] = v4 };
等價(jià)于
int a[6] = { 0, v1, v2, 0, v4, 0 };
當(dāng)下標(biāo)是字符或者屬于enum類型時(shí),標(biāo)識數(shù)組初始化語句的元素特別有用。例如: int whitespace[256] = { [' '] = 1, ['/t'] = 1, ['/h'] = 1, ['/f'] = 1, ['/n'] = 1, ['/r'] = 1 };
你也可以在“=”前面寫上一系列的“.fieldname”和“[index]”指示符來指定一個(gè)要初始化的嵌套的子對象;這個(gè)列表是相對于和最近的花括號對一致的子對象。比如,用上面的struct point聲明: struct point ptarray[10] = { [2].y = yv2, [2].x = xv2, [0].x = xv0 };
如果同一個(gè)成員被初始化多次,它將從最后一次初始化中取值。如果任何這樣的覆蓋初始化有副作用,副作用發(fā)生與否是非指定的。目前,gcc會舍棄它們并產(chǎn)生一個(gè)警告。
5.23 case范圍
你可以在單個(gè)case標(biāo)簽中指定一系列連續(xù)的值,就像這樣: case low ... high:
這和單獨(dú)的case標(biāo)簽的合適數(shù)字有同樣的效果,每個(gè)對應(yīng)包含在從low到high中的一個(gè)整數(shù)值。
這個(gè)特性對一系列的ASCII字符代碼特別有用:
case 'A' ... 'Z':
當(dāng)心:在...周圍寫上空格,否則當(dāng)你把它和整數(shù)值一起使用時(shí),它就會被解析出錯(cuò)。例如,這樣寫:
case 1 ... 5:
而不是:
case 1...5:
5.24 向共同體類型轉(zhuǎn)換
向共同體類型轉(zhuǎn)換和其它轉(zhuǎn)換類似,除了指定的類型是一個(gè)共同體類型。你可以用union tag或一個(gè)typedef名字來指定類型。向共同體轉(zhuǎn)換實(shí)際上卻是一個(gè)構(gòu)造,而不是一個(gè)轉(zhuǎn)換,因此不像普通轉(zhuǎn)換那樣產(chǎn)生一個(gè)左值。(參考5.21節(jié)復(fù)合文字) 可以向共同體類型轉(zhuǎn)換的類型是共同體中成員的類型。所以,給定下面的共同體和變量:
union foo { int i; double d; }; int x; double y;
x和y都能夠被轉(zhuǎn)換成類型union foo。
把這種轉(zhuǎn)換作為給共同體變量賦值的右側(cè)和在這個(gè)共同體的成員中存儲是等價(jià)的:
union foo u; ... u = (union foo) x == u.i = x u = (union foo) y == u.d = y
你也可以使用共同體轉(zhuǎn)換作為函數(shù)參數(shù)。
void hack (union foo); ... hack ((union foo) x);
5.25 混合聲明和代碼
ISO C99和ISO C++允許聲明和代碼在復(fù)合語句中自由地混合。作為一個(gè)擴(kuò)展,GCC在C89模式下也允許這樣。比如,你可以: int i; ... i++; int j = i + 2;
每個(gè)標(biāo)識符從它被聲明的地方到閉合控制塊結(jié)束都是可見的。
5.26 聲明函數(shù)的屬性
在GNU C中,你可以聲明關(guān)于在你程序中調(diào)用的函數(shù)的某些東西,來幫助編譯器優(yōu)化函數(shù)調(diào)用和更仔細(xì)地檢查你的代碼。 關(guān)鍵字__attribute__允許你在聲明時(shí)指定特殊的屬性。跟在這個(gè)關(guān)鍵字后面的是雙重圓括號里面的屬性說明。有十四個(gè)屬性noreturn, pure, const, format, format_arg, no_instrument_function, section, constructor, destructor, unused, weak, malloc, alias and no_check_memory_usage是目前為函數(shù)定義的。在特別的目標(biāo)系統(tǒng)上,也給函數(shù)定義了一些其它屬性。其它屬性,包括section都為變量聲明(參考5.33節(jié) 指定變量屬性)和類型(參考5.34節(jié) 指定類型屬性)所支持。
你也可以把“__”放在每個(gè)關(guān)鍵字的前面和后面來指定屬性。這允許你在頭文件中使用它們,而不用關(guān)心一個(gè)可能有相同名字的宏。比如,你可以使用__noreturn__而不是noreturn。
參見5.27節(jié) 屬性語法來了解使用屬性的精確語法細(xì)節(jié)。
noreturn
一些標(biāo)準(zhǔn)庫函數(shù),就像abort和exit,不能返回。GCC會自動了解到這一點(diǎn)。一些程序定義它們自己的從不返回的函數(shù)。你可以把它們聲明為noreturn來告訴編譯器這個(gè)事實(shí)。比如,
void fatal () __attribute__ ((noreturn)); void fatal (...) { ... /* Print error message. */ ... exit (1); } 關(guān)鍵字noreturn告訴編譯器去假設(shè)fatal不能返回。那它就能做優(yōu)化,而不用理會如果fatal返回會發(fā)生什么。這會產(chǎn)生稍微好一點(diǎn)兒的代碼。更重要的是,它有助于避免未初始化變量的偽造警告。
不要假設(shè)調(diào)用函數(shù)保存的寄存器在調(diào)用noreturn函數(shù)之前被恢復(fù)。
對于一個(gè)noreturn函數(shù),有一個(gè)除void之外的返回類型是毫無意義的。
在早于2.5版的GCC中沒有實(shí)現(xiàn)noreturn屬性。聲明不返回值的函數(shù)的一個(gè)可替代的方法,在當(dāng)前版本和一些舊的版本中都可以工作,如下:
typedef void voidfn (); volatile voidfn fatal;
pure
很多函數(shù)除了返回值外沒有作用,而且它們的返回值只取決于參數(shù)和/或全局變量。這樣的一個(gè)函數(shù)可能依附于普通的子表達(dá)式的消除和循環(huán)的優(yōu)化,就像一個(gè)算術(shù)操作符那樣。這些函數(shù)應(yīng)該用屬性pure來聲明。例如,
int square (int) __attribute__ ((pure));
說明假定的函數(shù)square可以安全地比程序中說的少調(diào)用幾次。(在循環(huán)中保存計(jì)算結(jié)果,而不必每次都計(jì)算)
pure函數(shù)的一些常見例子是strlen和memcmp。有趣的非pure函數(shù)是帶無限循環(huán),或者那些取決于易失性內(nèi)存或其它系統(tǒng)資源的函數(shù),它們可能在兩次連續(xù)的調(diào)用中間改變(比如在多線程環(huán)境中的feof)。pure屬性在GCC早于2.96的版本中沒有實(shí)現(xiàn)。
const
很多函數(shù)不檢查除它們的參數(shù)外的任何值,而且除返回值外沒有任何作用。基本上,這比上面的pure屬性稍微更嚴(yán)格一些,既然函數(shù)不允許去讀全局內(nèi)存。
注意,帶指針參數(shù),而且檢查所指向數(shù)據(jù)的函數(shù)不能聲明為const。同樣的,調(diào)用非const函數(shù)的函數(shù)通常也不能是const。一個(gè)const函數(shù)返回void是沒任何意義的。
屬性const在GCC早于2.5的版本中沒有實(shí)現(xiàn)。聲明一個(gè)函數(shù)沒有副作用的一個(gè)可替代的方式,能夠在當(dāng)前版本和一些舊的版本中工作,如下:
typedef int intfn (); extern const intfn square;
這種方法在2.6.0以后的GNU C++不起作用,既然語言指明const必須依附于返回值。
format (archetype, string-index, first-to-check)
format屬性指明一個(gè)函數(shù)使用printf,scanf,strftime或strfmon風(fēng)格的參數(shù),應(yīng)該通過格式化字符串進(jìn)行類型檢查。比如,聲明:
extern int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3)));
會促使編譯器檢查調(diào)用my_printf中的參數(shù)和printf風(fēng)格的格式化字符串參數(shù)my_format是否一致。
參數(shù)archetype決定格式化字符串是怎么被解釋的,而且應(yīng)當(dāng)是printf,scanf,strftime或strfmon。(你也可以使用__printf__,__scanf__,__strftime__或者_(dá)_strfmon__。)參數(shù)string-index指定哪個(gè)參數(shù)是格式化字符串參數(shù)(從1開始),而first-to-check是通過格式化字符串檢查的第一個(gè)參數(shù)。對于參數(shù)不可用來檢查的函數(shù)(比如vprintf),指定第三個(gè)參數(shù)為0。在這種情況下,編譯器只檢查格式化字符串的一致性。對于strftime格式,第三個(gè)參數(shù)需要為0。
在上面的例子中,格式化字符串(my_format)是my_printf函數(shù)的第二個(gè)參數(shù),而且要檢查的函數(shù)從第三個(gè)參數(shù)開始,所以format屬性的正確參數(shù)是2和3。
format屬性允許你去識別你自己的把格式化字符串作為參數(shù)的函數(shù),所以GCC可以檢查對這些函數(shù)的調(diào)用錯(cuò)誤。編譯器總是(除非使用了“-ffreestanding”)為標(biāo)準(zhǔn)庫函數(shù)printf,fprintf,sprintf,scanf,fscanf,sscanf,strftime,vprintf,vfprintf和vsprintf檢查格式,當(dāng)請求這種警告時(shí)(使用“-Wformat”),所以沒有必要修改頭文件stdio.h。在C99模式下,函數(shù)snprintf,vsnprintf,vscanf,vfscanf和vsscanf也被檢查。參考“控制C方言的選項(xiàng)”一節(jié)。。
format_arg (string-index)
format_arg屬性指明一個(gè)函數(shù)使用printf,scanf,strftime或strfmon風(fēng)格的參數(shù),而且修改它(比如,把它翻譯成其它語言),所以結(jié)果能夠傳遞給一個(gè)printf,scanf,strftime或strfmon風(fēng)格的函數(shù)(格式化函數(shù)的其余參數(shù)和它們在不修改字符串的函數(shù)中一樣)。例如,聲明:
extern char * my_dgettext (char *my_domain, const char *my_format) __attribute__ ((format_arg (2)));
促使編譯器檢查調(diào)用printf,scanf,strftime或strfmon類型的函數(shù)中的參數(shù),其格式化字符串參數(shù)是函數(shù)my_dgettext函數(shù)的調(diào)用,和格式化字符串參數(shù)my_format是否一致。如果format_arg屬性沒有被指定,在對格式化函數(shù)的這種中,編譯器所能告知的一切是格式化字符串參數(shù)不是常量;當(dāng)使用“-Wformat-nonliteral”時(shí),這會產(chǎn)生一個(gè)警告,但如果沒有屬性,調(diào)用將不會被檢查。 參數(shù)string-index指定哪個(gè)參數(shù)是格式化字符串(從1開始)。
format-arg屬性允許你去識別你自己的修改格式化字符串的函數(shù),那樣,GCC可以檢查對printf,scanf,strftime或strfmon類型函數(shù)的調(diào)用,它們的操作數(shù)是對你自己的一個(gè)函數(shù)的調(diào)用。編譯器總是以這種方式對待gettext,dgettext和 dcgettext,除了當(dāng)嚴(yán)格的ISO C支持通過“-ansi”或者一個(gè)合適的“-std”選項(xiàng)請求時(shí),或者“-ffreestanding”使用時(shí)。參考“控制C方言的選項(xiàng)”一節(jié)。
no_instrument_function
如果給定“-finstrument-functions”,在大多數(shù)用戶編譯的函數(shù)的入口和出口會生成對概要分析函數(shù)的調(diào)用。有這個(gè)屬性的函數(shù)將不會被測量。
section ("section-name")
通常,編譯器會把它生成的代碼放入text部分。有時(shí),然而,你需要額外的部分,或者你需要某些特別的函數(shù)出現(xiàn)在特別的部分。section屬性指定一個(gè)函數(shù)放入一個(gè)特別的部分。比如,聲明:
extern void foobar (void) __attribute__ ((section ("bar")));
把函數(shù)foobar放進(jìn)bar部分。
一些文件格式不支持任意部分,所以section屬性并不是在所有平臺上可用的。如果你需要把一個(gè)模塊的全部內(nèi)容映射到一個(gè)特別的部分,考慮使用鏈接器的工具。
constructor
destructor
constructor屬性促使函數(shù)在執(zhí)行main()之前自動被調(diào)用。類似地,destructor屬性促使函數(shù)在main()函數(shù)完成或exit()被調(diào)用完之后自動被調(diào)用。有這些屬性的函數(shù)對初始化在程序執(zhí)行期間間接使用的數(shù)據(jù)很有用。
這些屬性目前沒有為Objective C所實(shí)現(xiàn)。
unused
這個(gè)屬性,依附于一個(gè)函數(shù),意味著這個(gè)函數(shù)將可能打算不被使用。GCC將不會為這個(gè)函數(shù)產(chǎn)生一個(gè)警告。GNU C++目前不支持這個(gè)屬性,因?yàn)闆]有參數(shù)的定義在C++中是合法的。
weak
weak屬性促使聲明被作為一個(gè)弱符號導(dǎo)出,而不是全局符號。這在定義庫函數(shù)時(shí)非常有用,它們能夠被用戶代碼覆蓋,雖然它也可以和非函數(shù)聲明一起使用。弱符號被ELF目標(biāo)文件所支持,而且當(dāng)使用GNU匯編器和鏈接器時(shí)也被a.out目標(biāo)文件支持。
malloc
malloc屬性用來告訴編譯器一個(gè)函數(shù)可以被當(dāng)做malloc函數(shù)那樣。編譯器假設(shè)對malloc的調(diào)用產(chǎn)生一個(gè)不能替換成其它東西的指針。
alias ("target")
alias屬性促使這個(gè)聲明被作為另一個(gè)必須被指定的符號的別名導(dǎo)出。例如,
void __f () { /* do something */; } void f () __attribute__ ((weak, alias ("__f")));
聲明“f”是“__f”的一個(gè)弱別名。在C++中,目標(biāo)的重整名字必須被使用。
并不是所有的目標(biāo)機(jī)器支持這個(gè)屬性。
no_check_memory_usage
no_check_memory_usage屬性促使GCC忽略內(nèi)存引用的檢查,當(dāng)它為函數(shù)生成代碼時(shí)。通常如果你指定“-fcheck-memory-usage”(參考“t/newbie/gcc.htm#3.18">3.18 代碼生成轉(zhuǎn)換選項(xiàng)”一節(jié)),GCC在大多數(shù)內(nèi)存訪問之前生成調(diào)用支持的例程,以便允許支持代碼記錄用法和探測未初始化或未分配存儲空間的使用。既然GCC不能恰當(dāng)處理asm語句,它們不允許出現(xiàn)在這樣的函數(shù)中。如果你用這個(gè)屬性聲明了一個(gè)函數(shù) ,GCC將會為那個(gè)函數(shù)生成內(nèi)存檢查代碼,允許使用asm語句,而不必用不同選項(xiàng)去編譯那個(gè)函數(shù)。這允許你編寫自己的支持例程如果你愿意,而不會導(dǎo)致無限遞歸,如果它們用“-fcheck-memory-usage”編譯的話。
regparm (number)
在Intel 386上,regparm屬性促使編譯器用寄存器EAX,EDX,和ECX,而不是堆棧,來傳遞最多number個(gè)參數(shù)。帶可變參數(shù)數(shù)目的函數(shù)將會繼續(xù)在堆棧上傳遞它們的參數(shù)。
stdcall
在Intel 386上,stdcall屬性促使編譯器假定被調(diào)用的函數(shù)將會彈出用來傳遞參數(shù)的堆棧空間,除非它適用可變數(shù)目的參數(shù)。
cdecl
在Intel 386上,cdecl屬性促使編譯器假定調(diào)用函數(shù)將會彈出用來傳遞參數(shù)的堆??臻g。這對覆蓋“-mrtd”開關(guān)的作用很有幫助。
PowerPC上Windows NT的編譯器目前忽略cdecl屬性。
longcall
在RS/6000和PowerPC上,longcall屬性促使編譯器總是通過指針來調(diào)用函數(shù),所以在距當(dāng)前位置超過64MB(67108864字節(jié))的函數(shù)也能夠被調(diào)用。
long_call/short_call
這個(gè)屬性允許指定如果在ARM上調(diào)用一個(gè)特別的函數(shù)。兩個(gè)屬性都覆蓋“-mlong-calls”命令行開關(guān)和#pragma long_calls設(shè)置。long_call屬性促使編譯器總是通過先裝入它的地址到一個(gè)寄存器再使用那個(gè)寄存器的內(nèi)容來調(diào)用這個(gè)函數(shù)。short_call屬性總是直接把從調(diào)用現(xiàn)場到函數(shù)的偏移量放進(jìn)‘BL’指令中。
dllimport
在運(yùn)行Windows NT的PowerPC上,dllimport屬性促使編譯器通過一個(gè)全局指針去調(diào)用函數(shù),這個(gè)指針指向由Windows NT的dll庫安裝的函數(shù)指針。指針名是通過組合__imp__和函數(shù)名來形成的。
dllexport
在運(yùn)行Windows NT的PowerPC上,dllexport屬性促使編譯器提供一個(gè)指向函數(shù)指針的全局指針,那樣它就能用dllimport屬性調(diào)用。指針名是通過組合__imp__和函數(shù)名來形成的。
exception (except-func [, except-arg])
在運(yùn)行Windows NT的PowerPC上,exception屬性促使編譯器修改為聲明函數(shù)導(dǎo)出的結(jié)構(gòu)化異常表的表項(xiàng)。字符串或標(biāo)識符except-func被放在結(jié)構(gòu)化異常表的第三項(xiàng)中。它代表一個(gè)函數(shù),當(dāng)異常發(fā)生時(shí)被異常處理機(jī)制調(diào)用。如果它被指定,字符串或標(biāo)識符except-arg被放在結(jié)構(gòu)化異常表的第四項(xiàng)中。
function_vector
在H8/300和H8/300H上使用這個(gè)選項(xiàng)用表明指定的函數(shù)應(yīng)該通過函數(shù)向量來調(diào)用。通過函數(shù)向量調(diào)用函數(shù)將會減少代碼尺寸,然而,函數(shù)向量有受限的大小(在H8/300上最多128項(xiàng),H8/300H上64項(xiàng)),而且和中斷向量共享空間。
為此選項(xiàng),你必須使用2.7版或以后的GNU binutils中的GAS和GLD才能正確工作。
interrupt
在H8/300,H8/300H和SH上使用這個(gè)選項(xiàng)表明指定的函數(shù)是一個(gè)中斷處理程序。當(dāng)這個(gè)屬性存在時(shí),編譯器將會生成函數(shù)入口和出口工序,為適應(yīng)在中斷處理程序中的使用。
注意,H8/300,H8/300H和SH處理器的中斷處理程序可以通過interrupt_handler屬性來指定。
注意,在AVR上,中斷將會在函數(shù)里面被啟用。
注意,在ARM上,你可以通過給中斷屬性添加一個(gè)可選的參數(shù)指定要處理的中斷類型,就像這樣:
void f () __attribute__ ((interrupt ("IRQ")));
這個(gè)參數(shù)的允許值是:IRQ, FIQ, SWI, ABORT和UNDEF。
interrupt_handler
在H8/300,H8/300H和SH上使用這個(gè)選項(xiàng)表明指定的函數(shù)是一個(gè)中斷處理程序。當(dāng)這個(gè)屬性存在時(shí),編譯器將會生成函數(shù)入口和出口工序,為適應(yīng)在中斷處理程序中的使用。
sp_switch
在SH上使用這個(gè)選項(xiàng)表明一個(gè)interrupt_handler函數(shù)應(yīng)該切換到一個(gè)可替代的堆棧上。它期待一個(gè)字符串參數(shù),用來命名一個(gè)存放替代堆棧地址的全局變量。
void *alt_stack; void f () __attribute__ ((interrupt_handler, sp_switch ("alt_stack")));
trap_exit
在SH上為interrupt_handle使用此選項(xiàng)來使用trapa而不是rte來返回。這個(gè)屬性期待一個(gè)整數(shù)參數(shù)來指定要使用的陷阱號。
eightbit_data
在H8/300和H8/300H上使用此選項(xiàng)來表明指定的變量應(yīng)該放到8比特?cái)?shù)據(jù)區(qū)。編譯器將會為在8比特?cái)?shù)據(jù)區(qū)上的操作生成更高效的代碼。注意8比特?cái)?shù)據(jù)區(qū)的大小限制在256字節(jié)。
tiny_data
在H8/300H上使用此選項(xiàng)來表明指定的變量應(yīng)該放到微數(shù)據(jù)區(qū)。編譯器將會為在微數(shù)據(jù)區(qū)中存取數(shù)據(jù)生成更高效的代碼。注意微數(shù)據(jù)區(qū)限制在稍微低于32K字節(jié)。
signal
在AVR上使用此選項(xiàng)來表明指定的函數(shù)是一個(gè)信號處理程序。當(dāng)這個(gè)屬性存在時(shí),編譯器將會生成函數(shù)入口和出口工序,為適應(yīng)在信號處理程序中的使用。在函數(shù)內(nèi)部,中斷將會被屏蔽。
naked
在ARM或AVR移植上使用此選項(xiàng)來表明指定的函數(shù)不需要由編譯器來生成開場白/收場白工序。由程序員來提供這些工序。
model (model-name)
在M32R/D上使用這個(gè)屬性來設(shè)置對象和函數(shù)生成代碼的可尋址性。標(biāo)識符model-name是small,medium或large其中之一,各代表一種編碼模型。
small模型對象駐留在內(nèi)存的低16MB中(所以它們的地址可以用ld24指令來加載),可用bl指令調(diào)用。
medium模型對象可能駐留在32位地址空間的任何地方(編譯器將會生成seth/add3指令來加載它們的地址),可用bl指令調(diào)用。
large模型對象可能駐留在32位地址空間的任何地方(編譯器將會生成seth/add3指令來加載它們的地址),而且可能使用bl指令夠不到(編譯器將會生成慢得多的seth/add3/jl指令序列)。
你可以在一個(gè)聲明中指定多重屬性,通過在雙圓括號中用逗號來把它們分割開,或者在一個(gè)屬性聲明后緊跟另一個(gè)屬性聲明。
一些人反對__attribute__特性,建議使用ISO C的#pragma來替代。在設(shè)計(jì)__attribute__時(shí),有兩條原因不適合這么做。
不可能從宏中生成#pragma命令。
沒有有效說明同樣的#pragma在另一個(gè)編譯器中可能意味著什么。
這兩條原因適用于幾乎任何提議#pragma的應(yīng)用程序。為任何東西使用#pragma基本上都是一個(gè)錯(cuò)誤。 ISO C99標(biāo)準(zhǔn)包括_Pragma,它現(xiàn)在允許從宏中生成pragma。另外,#pragma GCC名字空間現(xiàn)在為GCC特定的pragma使用。然而,人們已經(jīng)發(fā)現(xiàn)使用__attribute__來實(shí)現(xiàn)到相關(guān)聲明的自然的附件屬性很方便,而為構(gòu)造而使用的#pragma GCC沒有自然地形成語法的一部分。查看“C預(yù)處理器”中的“多種預(yù)處理命令”一部分。
5.27 屬性語法
這一段說明了在C語言中,使用到__attribute__的語法和屬性說明符綁定的概念。一些細(xì)節(jié)也許對C++和Objective C有所不同。由于對屬性不合適的語法,這里描述的一些格式可能不能在所有情況下成功解析。 看5.26節(jié),聲明函數(shù)的屬性,了解施加于函數(shù)的屬性語義的細(xì)節(jié)???.33節(jié),說明變量屬性,了解施加于變量的屬性語義的細(xì)節(jié)。看5.34節(jié)指定類型屬性,了解施加與結(jié)構(gòu)體,共用體,和枚舉類型的屬性語義的細(xì)節(jié)。
屬性說明符是的格式是:__attribute__((屬性列表))。屬性列表是一個(gè)可為空的由逗號分隔的屬性序列,其中的屬性類型如下:
空。空屬性會被忽略。
字(可能是一個(gè)標(biāo)識符如unused,或者一個(gè)保留字如const)。
在跟在后邊的圓括號中有屬性的參數(shù)的字。這些參數(shù)有如下的格式:
標(biāo)識符。比如,模式屬性使用這個(gè)格式。
跟有逗號或非空的由逗號分隔的表達(dá)式表。比如,格式化屬性使用這個(gè)格式。 可為空的由逗號分隔的表達(dá)式表。比如,格式化參數(shù)使用這個(gè)格式和一個(gè)單獨(dú)整型常量表達(dá)式列表,并且別名屬性使用這個(gè)格式和一個(gè)單獨(dú)的字符串常量列表。 屬性說明符列表是一個(gè)由一個(gè)或多個(gè)屬性說明符組成的序列,不被任何其它標(biāo)志分開。
屬性說明符列表可能跟在一個(gè)標(biāo)簽后的冒號出現(xiàn),除了case或default中的標(biāo)簽。唯一的屬性使用在一個(gè)未使用的標(biāo)簽后是合理的。這個(gè)特征被設(shè)計(jì)成代碼被包含而未使用的標(biāo)簽但是在編譯是使用了"-Wall"參數(shù)。它也許不常用與人工編寫的代碼中,雖然它應(yīng)該在代碼需要跳到一個(gè)包含在一段有#ifdef說明的條件編譯的程序段中很有用。
屬性說明符列表可以作為結(jié)構(gòu)體、共用體或枚舉說明符的一部分出現(xiàn)。它既可以直接跟在結(jié)構(gòu)體、共用體或枚舉說明符后,也可以緊貼大括號之后。如果結(jié)構(gòu)體、共用體或枚舉類型沒有在使用屬性說明符列表中用到的說明符定義,也就是說在如下這樣的用法中struct __attribute__((foo))沒有跟空的大括號,它會被忽略。在用到屬性說明符的地方,跟它靠近的大括號,它們會被認(rèn)為和結(jié)構(gòu)體、共用體或被定義的枚舉類型有聯(lián)系,不同任何出現(xiàn)在包含聲明的類型說明符,并且類型直到屬性說明符之后才結(jié)束。
否則,屬性說明符作為聲明的一部分出現(xiàn),計(jì)算未命名參數(shù)和類型明聲明,并且和這個(gè)聲明相關(guān)(有可能內(nèi)嵌在另外一個(gè)聲明中,例如在參數(shù)聲明的時(shí)候)。 將來屬性說明符在一些地方也任何用于特殊聲明符不超出代替聲明;這些情況將在一下提到。在屬性說明符被用于在函數(shù)或數(shù)組中說明參數(shù)的地方,它也許用于函數(shù) 或數(shù)組而不是指向一個(gè)會被隱含轉(zhuǎn)換的參數(shù)的指針,但這不僅是正確的工具。
任何在可能包含屬性說明符的聲明開始處的說明符與修飾符列表,抑或沒有這樣一個(gè)列表也許在上下文包含存儲類說明符。(一些屬性盡管本質(zhì)自然是類型說 明符,并且僅在需要使用存儲類說明符的地方是合理的,例如section。)對這個(gè)語法有一個(gè)必要的限制:第一,在函數(shù)定義中的老式風(fēng)格的參數(shù)聲明無法有 屬性說明符開頭,這種情況尚不提供)。在一些其它情況下,屬性說明符被允許使用這種語法但不被編譯器所支持。所有的屬性說明符在這里被當(dāng)做正割聲明的一部 分。在逐步被廢棄的在int類型默認(rèn)省略了類型說明符的用法的地方,這樣的說明符和修飾符列表可能是沒有任何其它說明符或修飾符的屬性說明符列表。
在不止一個(gè)標(biāo)識符使用單獨(dú)的說明符或修飾符的聲明中的由逗號分隔的說明符列表中,屬性說明符列表可能直接在一個(gè)說明符之前出現(xiàn)(第一個(gè)除外)。目 前,這樣的屬性說明符不僅適用于被出現(xiàn)在自己的聲明中的標(biāo)識符,也可以用于在此聲明明中此后聲明的所有標(biāo)識符,但是將來它們可能只能用于那樣的單獨(dú)的標(biāo)識 符。例如:__attribute__((noreturn)) void d0 (void), __attribute__((format(printf, 1, 2))) d1 (const char *, ...), d2 (void)
無返回值屬性用于所有的函數(shù)聲明中;格式化屬性會只用于d1,但是目前也用于d2(并且由此造成錯(cuò)誤)。
屬性說明符列表可能直接出現(xiàn)在逗號、等號或分號之前,終止標(biāo)識符的說明除過函數(shù)定義。目前,這樣的屬性說明符用于已聲明的對象或函數(shù),但是將來它們將附屬與相鄰的最遠(yuǎn)的說明符。在簡單情況下沒有區(qū)別,但是例如在void (****f)(void) __attribute__((noreturn))這 樣的聲明中,當(dāng)前無返回值的屬性用于f,這就會造成從f起不是一個(gè)函數(shù)的警告,但是將來它也許用于函數(shù)****f。在這種情況中的屬性的明確的語言符號將 不用于定義。在對象或函數(shù)的匯編名稱處被說明(看5.37節(jié)控制用于匯編代碼的名稱),當(dāng)前,屬性必須跟隨與asm說明之后;將來,在asm說明之前的屬 性可能用于鄰近的聲明符,并且它那些在它之的被用于已聲明的對象或函數(shù)。
將來,屬性說明符可能被允許在函數(shù)定義的聲明符后出現(xiàn)(在任意老式風(fēng)格參數(shù)聲明或函數(shù)題之前)。
屬性說明符列表可能出現(xiàn)在內(nèi)嵌聲明符的開始。目前,這種用法有一些限制:屬性用于在這個(gè)聲明中聲明過的標(biāo)識符和所有之后的聲明過的標(biāo)識符(如果它包 括一個(gè)逗號分隔的聲明符列表),而不僅是用于特定的聲明符。當(dāng)屬性說明符跟在指針聲明符“*”之后時(shí),它們應(yīng)該出現(xiàn)在任意一種修飾符序列之后,并且不能和 它們混在一起。接下來的陳述預(yù)期將來的語言符號僅使這種語法更有用。如果你對標(biāo)準(zhǔn)ISO C的聲明符的說明格式熟悉的話,它將更好理解。
考慮T D1這樣的聲明(像C99標(biāo)準(zhǔn)6.7.5第四段中的子句),T包含聲明說明符的地方說明一個(gè)Type類型(比如int)并且D1是一個(gè)包含標(biāo)識符的標(biāo)志的聲明符。類型位標(biāo)志被說明用來導(dǎo)出類型不包括在屬性說明符中的聲明符就像標(biāo)準(zhǔn)ISO C那樣。
如果D1有(屬性說明符列表 D)這樣的格式,并且T D這樣的聲明為標(biāo)志說明了“導(dǎo)出聲明符類型列表 類型”的類型,這樣T D1為標(biāo)志說明了“導(dǎo)出聲明符類型列表 屬性說明符列表 類型”的類型。
如果D1有 * 類型修飾符和屬性說明符列表 D這樣的格式,并且T D這樣的聲明為標(biāo)志說明“導(dǎo)出聲明符類型列表 類型”的類型,則T D1為標(biāo)志說明“導(dǎo)出聲明符列表 類型修飾符和屬性說明符列表 類型”的類型。
例如,void (__attribute__((noreturn)) ****f)();說明“指向返回空的無返回值函數(shù)的指針的指針的指針”的類型。作為另外的例子,char *__attribute__((aligned(8))) *f;說明“指向8位寬度的指向char型數(shù)據(jù)的指針的指針”的類型。再次注意這個(gè)陳述是被期待將來的語法符號,不是當(dāng)前實(shí)現(xiàn)的。
5.28 原型和老式風(fēng)格的函數(shù)定義
GNU C對ISO C到允許函數(shù)原型忽略一個(gè)新的老式風(fēng)格的無原型定義??紤]一下的例子: /* 除非老式的編譯器才使用原型*/
/* Use prototypes unless the compiler is old-fashioned. */ #ifdef __STDC__ #define P(x) x #else #define P(x) () #endif /* 原型函數(shù)聲明. */ int isroot P((uid_t)); /* 老式風(fēng)格的函數(shù)定義. */ int isroot (x) /* ??? lossage here ??? */ uid_t x; { return x == 0; }
設(shè)想類型uid_t恰好是短整型。ISO C是決不允許這個(gè)例子的,因?yàn)樵诶鲜斤L(fēng)格中的參數(shù)子字被提升了。因此在這個(gè)例子中,函數(shù)定義的參數(shù)確實(shí)是個(gè)和原型參數(shù)的短整型類型不匹配的整型。
ISO C的這個(gè)限制是它難以寫出可以移植到傳統(tǒng)C編譯器上的代碼,因?yàn)槌绦騿T不知道uit_t類型是短整型、整型還是長整型。因此, 像GNU C允許在這些情況下原型忽略新的老式風(fēng)格的定義。更嚴(yán)謹(jǐn)?shù)氖窃贕NU C中,函數(shù)原型參數(shù)類型如果一個(gè)錢類型想后來的類型在提升以前一樣,則忽略更新的老式風(fēng)格定義說明的參數(shù)。因此在GNU C中上面這些個(gè)例子等價(jià)與下面的例子:
int isroot (uid_t); int isroot (uid_t x) { return x == 0; }
GNU C++ 不支持老式風(fēng)格函數(shù)定義,故這個(gè)擴(kuò)展和它是不相關(guān)的。
5.29 C++風(fēng)格注釋
在GNU C當(dāng)中,你可以使用C++風(fēng)格的注釋,就是一"http://"開頭并且一直到本行末。許多其它的C實(shí)現(xiàn)方案允許這樣的注釋,并且它們可能成為將來的C標(biāo)準(zhǔn)。但是,C++風(fēng)格注釋在你說明了"-ansi",或"-std"選項(xiàng)來聲明使用ISO C在C99之前的版本時(shí),將不會被識別,或者"-traditional"選項(xiàng),因?yàn)樗鼈兒蛡鹘y(tǒng)的被這樣的//*注釋*/分隔符分隔的結(jié)構(gòu)不相容。 5.30 標(biāo)識符名稱中的美元符
在GNU C當(dāng)中,你可以一般的在標(biāo)識符名稱中使用美元符。這是因?yàn)樵S多傳統(tǒng)的C實(shí)現(xiàn)方案允許這樣的標(biāo)識符。但是,標(biāo)識符中的美元符在少量目標(biāo)機(jī)器不被支持,典型原因是目標(biāo)匯編器不允許它們。 5.31 常量中的ESC字符
你可以在一個(gè)字符串或字符常量中使用序列'/e'來表示ASCII字符ESC。 5.32 詢問變量對齊方式
關(guān)鍵字__alignof__允許你詢問一個(gè)對象如何對齊,或者一個(gè)類型的需要的最小對齊。它的語法很像sizeof。 例如,不過目標(biāo)機(jī)器需要一個(gè)雙精度值來使一個(gè)8位的邊界對齊,這樣__alignof__(double)就是8.在許多RISC機(jī)器上就是這樣的。在很多傳統(tǒng)的機(jī)器設(shè)計(jì),__alignof__(double)是4或者甚至是2.
一些機(jī)器實(shí)際上從不需要對齊;它們允許參考任意數(shù)據(jù)類型甚至在一個(gè)奇數(shù)地址上。對這些機(jī)器,__alignof__報(bào)告類型的建議對齊。
當(dāng)__alignof__的操作數(shù)是一個(gè)左值而不是一個(gè)類型時(shí),這個(gè)值是這個(gè)左值已知有的最大對齊。它可能由于它的數(shù)據(jù)類有而有這個(gè)對齊,或者因?yàn)樗墙Y(jié)構(gòu)體的一部分并且從那個(gè)結(jié)構(gòu)體繼承了對齊。例如在這個(gè)聲明之后:
struct foo { int x; char y; } foo1;
__alignof__(foo1.y)的值可能是2或4,同__alignof__(int)相同,即使foo1.y的數(shù)據(jù)類型自己不需要任何對齊。
這是要求一個(gè)不完全的類型的對齊的錯(cuò)誤。
一個(gè)使你說明一個(gè)對象對齊的關(guān)聯(lián)特征是__attribute__ ((aligned (alignment)));請看下節(jié)。
5.33說明變量屬性
關(guān)鍵字__attribute__允許你說明變量或結(jié)構(gòu)體域的特殊屬性。這個(gè)關(guān)鍵字是跟有括在一對圓括號中的屬性說明?,F(xiàn)在給變量定義了八個(gè)屬性:aligned, mode, nocommon, packed, section, transparent_union, unused,和weak。在特定的目標(biāo)機(jī)器上定義了為變量定義了一些其它屬性。其它屬性可以用于函數(shù)(見5.26節(jié) 聲明函數(shù)屬性)和類型(見5.34節(jié)指定類型屬性)。其它q前端和末端可能定義更多的屬性(見C++語言的擴(kuò)展章節(jié))。 你可能也說明屬性有‘__’開頭并且跟在每一個(gè)關(guān)鍵字后邊。這允許你在頭文件中使用它們而不必?fù)?dān)心可能有同名的宏。例如,你可以使用__aligned__來代替aligned。
見5.27節(jié)屬性語法,了解正確使用屬性語法的細(xì)節(jié)。
aligned (對齊)
這個(gè)屬性以字節(jié)為單位說明一個(gè)變量或結(jié)構(gòu)體域的最小對齊。例如,這個(gè)聲明: int x __attribute__ ((aligned (16))) = 0; 造成編譯器在一個(gè)16字節(jié)的邊界上分配全局變量x。在68040上,這可以用在和一個(gè)匯編表達(dá)式連接去訪問需要16字節(jié)對齊對象的move16指令。 你也可以說明結(jié)構(gòu)體域的對齊。例如,創(chuàng)建一個(gè)雙字對齊的整型對,你可以寫: struct foo { int x[2] __attribute__ ((aligned (8))); }; 這是創(chuàng)建一個(gè)有兩個(gè)成員的共用體強(qiáng)制共用體雙字對齊的一個(gè)替換用法。
它可能不能說明函數(shù)的;函數(shù)的對齊是被機(jī)器的需求所決定且不能被改變的。你不能說明一個(gè)typedef定義的名稱的對齊因?yàn)檫@個(gè)名字僅僅是個(gè)別名,而不是特定的類型。 在前邊的例子,你可以明確說明你希望編譯器以給定的變量或結(jié)構(gòu)體域?qū)R(以字節(jié)位單位)。另外一種方案,你可以省略對齊因子并且進(jìn)讓編譯器以你為之編譯的目標(biāo)機(jī)器的最大有用對齊來對齊變量或域。例如,你可以寫: short array[3] __attribute__ ((aligned)); 無論何時(shí)你在一個(gè)要對齊的屬性說明中省略對齊因子,編譯器都會自動為聲明的變量或域設(shè)置對齊到你為之編譯的目標(biāo)機(jī)器上對 曾經(jīng)對任意數(shù)據(jù)類型用過的最大對齊。這樣做可以經(jīng)??梢允菑?fù)制動作更有效,因?yàn)榫幾g器可以使用任何指令復(fù)制最大的內(nèi)存塊當(dāng)執(zhí)行復(fù)制到你要求這樣對齊的變量 或域中或從這從中復(fù)制時(shí)。 對齊屬性可以僅僅增加對齊;但是你可以通過也說明packed屬性。見下邊。 注意這對齊屬性的可行性可能被你的連接器的固有限制所限制。在許多系統(tǒng)上,連接器僅僅可以把變量整理和對齊到一個(gè)特定的最大對齊。(對一些連接器,所支持的最大對齊可能非常非常小。)如果你的連接器幾近可以對齊變量最大8字節(jié),那么在__attribute__中說明aligned(16)仍然值提供給你8字節(jié)對齊。從你的連接器文檔中可以獲得更多的信息。 mode (模式) 這個(gè)屬性位聲明說明數(shù)據(jù)類型──任何類型都符合mode模式。這有效地使你獲取一個(gè)整型或浮點(diǎn)型的符合寬度。 你可能也說明'byte'或'__byte__'模式來表示模式同一字節(jié)的整數(shù)一致,'word'或'__word'來表示一個(gè)字的整數(shù)的模式,并且'pointer'或'__pointer__'來表示指針的模式。 nocommon 這個(gè)屬性說明要求GCC不要把放置一個(gè)變量為 "common"而是直接給它分配空間。如果你說明'-fno-common'標(biāo)志,GCC將對所有變量這樣做。 給變量說明nocommon屬性則提供初值為零。變量可能僅在一個(gè)源文件中初始化。
packed packed屬性說明一個(gè)變量或結(jié)構(gòu)體域應(yīng)該有盡可能小的對齊──一個(gè)變量一字節(jié)或一個(gè)域一字節(jié),除非你和對齊屬性一起說明了一個(gè)更大的值。 這里是一個(gè)域x被packed屬性說明的結(jié)構(gòu)體,所以它直接跟在a之后: struct foo { char a; int x[2] __attribute__ ((packed)); }; section("段名")
通常,編譯器將把對象放置在生成它的段中想data和bss。但是有時(shí)候你學(xué)要附加段,或者你需要一定特定的變量去出現(xiàn)在特殊的段中,例如去映射特殊的硬件。section屬性說明一個(gè)變量(或函數(shù))存在一個(gè)特定的段中。例如,這個(gè)小程序使用一些說明段名: 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); } 和一個(gè)全局變量的初始化定義一起使用section屬性,就像例子中那樣。GCC給出一個(gè)警告或者在未初始的變量聲明中忽略section屬性。
你可能由于連接器的工作方式僅同一個(gè)完全初始化全局定義使用section屬性。連接器要求每個(gè)對象被定義一次,例外的是未初始化變量假定竟如common(或bss)段而且可以多重“定義”。你可以強(qiáng)制一個(gè)變量帶'-fno-common'標(biāo)志初始化或nocommon屬性。 一些文件格式不支持隨意的段,所以section屬性不全適用于所有的平臺。如果你需要映射一個(gè)完全滿足的模塊到一個(gè)特定的段,慎重考慮使用連接器的設(shè)備來代替。 在Windows NT上, 在命名的段附加放置變量定義,這段也可以個(gè)在所有運(yùn)行的可執(zhí)行文件或DLL文件之間共享。例如,這個(gè)小程序通過將其放入命名的段并將該段標(biāo)記位共享而定義了共享數(shù)據(jù)。 int foo __attribute__((section ("shared"), shared)) = 0; int main() { /* Read and write foo. All running copies see the same value. */ return 0; } 你僅可以在section屬性完全初始化全局定義是使用shared屬性,因?yàn)檫B接器的工作方式。看section屬性來獲得更多的信息。
shared屬性僅適用于Windows NT。 這個(gè)屬性附屬與一個(gè)共用體型的函數(shù)參數(shù),意味著參數(shù)可能具有與共用體成員一致的任何類型,但是參數(shù)被當(dāng)做共用的第一個(gè)成 員的類型傳送???.34節(jié)說明類型屬性了解更多細(xì)節(jié)。你也能吧這個(gè)屬性用在對共用體數(shù)據(jù)類型適用typedef是;這樣它就可以被用在所有這個(gè)類型的函 數(shù)參數(shù)上。 unused 變量有這個(gè)屬性,意味著這個(gè)變量可能沒有被使用。GCC將不對這個(gè)變量產(chǎn)生警告。 weak weak屬性在5.26節(jié)聲明函數(shù)屬性已經(jīng)被陳述過。 model(模型名) 在M32R/D上使用這個(gè)屬性去設(shè)置對象的編址能力。這個(gè)標(biāo)識符模型名是small,medium或large中的一個(gè),代表每一個(gè)代碼模型。 小模型對象存在與低16MB內(nèi)存中(所以它們的地址可以和一個(gè)ld24指令一起裝入)。 中和大模型對象可能存在任何一個(gè)32位的地址空間中(編譯器將形成seth/add3指令來裝入它們的地址)。 說明多個(gè)屬性用逗號吧它們分隔開寫在一對圓括號中:例如,'__attribute__ ((aligned (16), packed))'。 5.34 指定類型屬性
當(dāng)你定義結(jié)構(gòu)體和共用體類型時(shí),關(guān)鍵字attribute允許你為這些類型指定特殊的屬性。這個(gè)關(guān)鍵字后面跟隨著包含雙parentheses的指定類型。四中屬性常被定義為:對齊(aligned),封裝(packed)型,透明共用體型(transparent-union)和未使用。另外的屬性則被定義為函數(shù)(看5.26段函數(shù)屬性的聲明)和變量(看5.33段指定變量屬性)。 你可以指定這些屬性在關(guān)鍵字之前或后面。這就使你能在頭文件應(yīng)用這種屬性而不必聲明 可能有同樣名字的宏 例如:你能用_aligned__ instead of aligned.
你可以在括號中放入枚舉的定義或聲明, 結(jié)構(gòu)或共用類型的定義和集裝屬性,括號后指定其屬性。
你也能在枚舉,結(jié)構(gòu)或共用間指定屬性的tag和名字而不是在)后。
看5.27 屬性語法,對于準(zhǔn)確使用語法屬性
aligned
這種屬性指定一個(gè)最小的隊(duì)列(按位算)為變量指定類型。例如,如下的聲明: struct S { short f[3]; } __attribute__ ((aligned (8))); typedef int more_aligned_int __attribute__ ((aligned (8))); 強(qiáng)制使編譯器確定每個(gè)類型為S的結(jié)構(gòu)體變量或者更多的組合整型,將被分配和匹配為至少8位。在可精簡效能結(jié)構(gòu)中,當(dāng)復(fù)制一個(gè)結(jié)構(gòu)體S變量到另外一個(gè)時(shí)。擁有所有的結(jié)構(gòu)體S 對齊8位的變量使編譯器能使用ldd和std,因此可以提高運(yùn)行效率。 {注意,任何給定的結(jié)構(gòu)體或共同體類型的對齊是ISO C標(biāo)準(zhǔn)所需的,至少是正在談?wù)摰慕Y(jié)構(gòu)體或共同體類型所有成員對齊的最小公倍數(shù)的一個(gè)完全的倍數(shù)。這就意為著你能有效的教正附屬于aligen對于結(jié)構(gòu)體和共用體隊(duì)列成員的屬性。但是以上例子中插入的注釋更加明顯,直觀和可讀對于編譯者來校正一個(gè)完全的結(jié)構(gòu)體或共用體類型組合。 封裝(packed) 這種屬性接近于枚舉,結(jié)構(gòu)或者共用類型的定義,指定一個(gè)所需最小的內(nèi)存用來代辦這種類型。 為結(jié)構(gòu)體和共用體指定這種屬性等效于為他們的每個(gè)成員指定集裝的的屬性。指定“短-枚舉”標(biāo)志等同于指定封裝的屬性在所有的枚舉定義。 你也可以僅僅在括號后面指定這種屬性,而不是為他定義類型的聲明,除非聲明也包括枚舉的定義。 transparent_union 這種屬性基于共用體的定義,表明一些函數(shù)的參數(shù)使用共用類型會被看作調(diào)用函數(shù)的一種特殊途徑.首先,參數(shù)與同透明共用體類型保持一致,能成為任何類型的共用體;不需要轉(zhuǎn)換. 加入共用體包含指針類型,一致的參數(shù)能成為一個(gè)空指針常量或空指針表達(dá)式;加入共用體包含空指針,一致參數(shù)能成為指針表達(dá)式。加入共用體成員類型是之至, 類型修飾符就像指定,就如一般的指針的轉(zhuǎn)換一樣。 第二,參數(shù)被傳給函數(shù)用到的調(diào)用轉(zhuǎn)換(透明共用體的第一個(gè)成員,不能調(diào)用轉(zhuǎn)換共用體本身。所有的成員必須擁有相同的機(jī)器代理;這就使參數(shù)傳輸能進(jìn)行了。 透明共用體擁有多種接口庫函數(shù)來處理兼容性問題。例如,假定等待函數(shù)必須要接受一種整型來遵從Posix,或者一個(gè)共用wait函數(shù)要適應(yīng)4.1BSD接口。記入wait函數(shù)的參數(shù)是空型,wait函數(shù)將接受各種型的參數(shù),但是它也能接受另外的指針型和這個(gè)將能使參數(shù)類型的檢測降低效用。而為"sys/wait.h"定義的接口如下: typedef union { int *__ip; union wait *__up; } wait_status_ptr_t __attribute__ ((__transparent_union__)); pid_t wait (wait_status_ptr_t); 這種接口允許整形或共用體等待參數(shù)的傳輸,用整型的調(diào)用轉(zhuǎn)換,這個(gè)程序能調(diào)用參數(shù)和類型: int w1 () { int w; return wait (&w); } int w2 () { union wait w; return wait (&w); } 在這個(gè)接口下,wait的執(zhí)行將是這樣: pid_t wait (wait_status_ptr_t p) { return waitpid (-1, p.__ip, 0); } 未用(unused) 當(dāng)應(yīng)用一種類型(包括共用體和結(jié)構(gòu)體),這個(gè)屬性意為著這種變量類型可能出席那不能使用的,GCC講不能產(chǎn)生警告對這些類型的變量,幾十變量什么作用都沒,這還經(jīng)常能導(dǎo)致鎖或線程的種類(通常被定義但不引用,但是包含構(gòu)建與消毀都存在非平凡簿記功能.) 5.35 內(nèi)聯(lián)函數(shù)像宏一樣快 通過聲明一個(gè)內(nèi)聯(lián)函數(shù),你就可以直接用GCC把函數(shù)源代碼和調(diào)用它的函數(shù)源代碼合成起來。這樣,通過消除高層的函數(shù)調(diào)用使得函數(shù)執(zhí)行更快;另外,如 果任何的實(shí)參是常數(shù),它們的已知值可能允許簡化從而不使所有的內(nèi)聯(lián)函數(shù)代碼在編譯時(shí)被包含進(jìn)來。這對代碼大小的影響幾乎是不可預(yù)知的;和內(nèi)聯(lián)函數(shù)相比,結(jié) 果代碼的大或小取決于具體情況。函數(shù)內(nèi)聯(lián)是一種優(yōu)化,而且只在優(yōu)化編譯上起作用。如果你沒有用'-0',那就沒有函數(shù)是真正內(nèi)聯(lián)的。 在C99標(biāo)準(zhǔn)里包含內(nèi)聯(lián)函數(shù),但是,當(dāng)前,GCC的實(shí)現(xiàn)和C99的標(biāo)準(zhǔn)要求確實(shí)存在差異。
像這樣,在函數(shù)聲明里用內(nèi)聯(lián)的關(guān)鍵字可以聲明一個(gè)函數(shù)內(nèi)聯(lián):
inline int inc (int *a) { (*a)++; }
(如果你正在寫一個(gè)要被包含在一個(gè)標(biāo)準(zhǔn)C程序的頭文件,請用 __inline__ 代替 inline.參見5.39節(jié) 備用關(guān)鍵字。)你同樣可以通過加選項(xiàng)`-finline-functions'使得所有足夠簡單的程序內(nèi)聯(lián)。
注意,在函數(shù)定義中的固定用法可以使函數(shù)不適合做內(nèi)聯(lián)。這些用法有:varargs函數(shù)的使用 ,alloca函數(shù)的使用, 可變大小數(shù)據(jù)類型的使用(參見5.14節(jié) 變長數(shù)組),goto 計(jì)算的使用 (參見 5.3節(jié) 可賦值標(biāo)簽), 非局部goto語句的使用, 以及嵌套函數(shù)的使用(參見 5.4節(jié)嵌套函數(shù) ).用`-Winline'可以在一個(gè)函數(shù)被標(biāo)記為內(nèi)聯(lián)而不能被取代時(shí)出現(xiàn)警告,并給出錯(cuò)誤原因。
注意在C和Objective C中, 不象C++那樣, 內(nèi)聯(lián)關(guān)鍵字不會影響函數(shù)的聯(lián)接。
GCC會自動將定義在C++程序內(nèi)class body中的元函數(shù)內(nèi)聯(lián)即使它們沒有被明確的聲明為內(nèi)聯(lián)。(你可以用`-fno-default-inline'忽略它;參見選項(xiàng)控制C++語法。)Options Controlling C++ Dialect
當(dāng)一個(gè)函數(shù)同時(shí)是靜態(tài)和內(nèi)聯(lián)時(shí),如果所有對這個(gè)函數(shù)的調(diào)用都被綜合在調(diào)用者中,而且函數(shù)地址從沒有被使用過,函數(shù)所有的匯編代碼都沒有被引用過。在這種情況下,GCC事實(shí)上不會為這個(gè)函數(shù)輸出匯編代碼,除非你指定選項(xiàng)`-fkeep-inline-functions'。由于各種原因一些函數(shù)調(diào)用不會被綜合(特殊的,調(diào)用在函數(shù)定義之前和在函數(shù)中的遞歸調(diào)用是不能被綜合的)。如果有一個(gè)非綜合調(diào)用,函數(shù)會被像平常一樣編譯出匯編代碼。如果程序引用了它的地址,這個(gè)函數(shù)也必須像平常一樣被編譯,因?yàn)榈刂肥遣荒鼙粌?nèi)聯(lián)的。
當(dāng)一個(gè)函數(shù)不是靜態(tài)時(shí),編譯器會假定在其它源文件中可能存在調(diào)用;由于在任何程序中全局符號(全局變量)只能被定義一次,函數(shù)一定不能在其它源文件中被定義,所以在那里的調(diào)用是不能被綜合的。因此,通常一個(gè)非內(nèi)聯(lián)函數(shù)總是被獨(dú)立的編譯。
如果你在函數(shù)定義中同時(shí)指定內(nèi)聯(lián)和外部援引,那么這個(gè)定義只會被用作內(nèi)聯(lián)。即使沒有明確的引用它的地址,函數(shù)也決不會被獨(dú)立編譯。這些地址變成了外部援引,就好像你只是聲明了函數(shù),沒有定義它一樣。
這種內(nèi)聯(lián)和外部援引的結(jié)合和宏定義的效果差不多。這種結(jié)合的用法是把函數(shù)定義和這些關(guān)鍵字放在一個(gè)頭文件中,把另外一份定義(缺少內(nèi)聯(lián)和外部援引) 的拷貝放在庫文件中。頭文件中的定義會使對這個(gè)函數(shù)的大多數(shù)調(diào)用成為內(nèi)聯(lián)。如果還有其它函數(shù)要用,它們將會查閱庫文件中專門的拷貝文件。
為了將來當(dāng) GCC 實(shí)現(xiàn) C99 標(biāo)準(zhǔn)語法中的內(nèi)聯(lián)函數(shù)時(shí)有兼容性,僅使用靜態(tài)內(nèi)聯(lián)是最好的。(當(dāng)`-std=gnu89'被指明時(shí),當(dāng)前語法可以保留可用部分,但最后的默認(rèn)將會是`-std=gnu99',并且它將會實(shí)現(xiàn)C99語法,盡管現(xiàn)在他并沒有被這樣做。)
GCC沒有優(yōu)化是沒有內(nèi)聯(lián)任何函數(shù)。內(nèi)聯(lián)好還是不好并不明確,既然這樣,但是我們發(fā)現(xiàn)沒有優(yōu)化時(shí)正確執(zhí)行是很困難的。所以我們做簡單的事,避開它。
5.36 匯編指令和C表達(dá)式 操作數(shù)
在匯編指令中用匯編語言,你可以指定用C語言中的表達(dá)式的操作數(shù)。這就意味著你不需要猜測哪個(gè)寄存器或存儲單元中包含你想要用的數(shù)據(jù)。 你必須指定一個(gè)盡可能像機(jī)器描述中的匯編指令模塊,為每個(gè)操作數(shù)加上一個(gè)被約束排成一列的操作數(shù)。
這是怎樣使用68881的 fsinx指令的例子:
asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
這里angle是一個(gè)用來輸入操作數(shù)的C表達(dá)式,result是輸出操作數(shù)。每個(gè)都有`"f"'作為它的操作數(shù)約束,說明需要一個(gè)浮點(diǎn)寄存器。`=f' 中的`='指明了這個(gè)操作數(shù)是一個(gè)輸出;所有輸出操作數(shù)的被約束使用`='。這種約束在同語言中被用于機(jī)器描述(參見20.7節(jié) 操作數(shù)約束)。
每個(gè)操作數(shù)在插入語中被一個(gè)后面跟著C表達(dá)式的約束操作數(shù)字符串描述。一個(gè)冒號隔開了匯編程序模塊和第一個(gè)輸出操作數(shù),另一個(gè)隔開了最后一個(gè)輸出操 作數(shù)和第一個(gè)輸入操作數(shù),即便要的話。逗號在每個(gè)群中隔開了不同的操作數(shù)。操作數(shù)的總數(shù)被限制在10或者被限制在操作數(shù)的最大數(shù)字,在任何機(jī)器描述中的任 何指令模型,無論哪一個(gè)都較大。
如果只有輸入操作數(shù)而沒有輸出操作數(shù),你應(yīng)該在輸出操作數(shù)在的位置的兩頭放兩個(gè)連續(xù)的冒號。
輸出操作數(shù)表達(dá)式必須是一個(gè)左值;編譯器可以檢測這點(diǎn)。輸入操作數(shù)不需要是左值。編譯器不能檢測操作數(shù)的數(shù)據(jù)類型對指令執(zhí)行來說是否合理。它不能解 析匯編指令模塊,它也不知道匯編指令的意思。甚至不能判斷它是否是一個(gè)有效的匯編輸入。擴(kuò)展匯編的特征是多數(shù)情況下用于機(jī)器指令,而編譯器本身卻不知道它 的存在。如果輸出表達(dá)式不可能是直接地址(比如說,它是一個(gè)位域),你的約束必須允許一個(gè)寄存器。那樣的話,GCC將會把寄存器當(dāng)作匯編程序的輸出,接下 來存儲寄存器內(nèi)容用作輸出。
普通的輸出操作數(shù)必須是只讀的;GCC 會假定這些在指令之前操作數(shù)中的左值是死的,并且不需要產(chǎn)生。擴(kuò)展匯編支持輸入-輸出或讀-寫操作數(shù)。用字符`+'可以指出這種操作數(shù)并且在輸出操作數(shù)中列出。
當(dāng)對一個(gè)讀寫操作數(shù)(或是操作數(shù)中只有幾位可以被改變)的約束允許一個(gè)而中選一的寄存器,你可以從邏輯上把它的作用分成兩個(gè)操作數(shù),一個(gè)是輸出操作 數(shù)和一個(gè)只寫輸出操作數(shù)。他們之間的關(guān)系是在指令執(zhí)行時(shí),被約束表示出他們需要在同一個(gè)位置。你可以對兩個(gè)操作數(shù)用同樣的C語言表示或不同的表示。這里我 們寫了一個(gè)結(jié)合指令(假想的)用后備地址寄存器當(dāng)作它的只讀資源操作數(shù),把foo作為它的讀寫目的地。
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
對操作數(shù)1來說,`"0"'約束是指它必須占據(jù)相同的位置相操作數(shù)0一樣。在約束中一個(gè)阿拉伯?dāng)?shù)字只被允許出現(xiàn)在輸入操作數(shù)中,而且,它必須提及到一個(gè)輸出操作數(shù)。
在約束中只有一個(gè)阿拉伯?dāng)?shù)字可以保證一個(gè)操作數(shù)會和其它操作數(shù)一樣出現(xiàn)在同一個(gè)地方。起碼的事實(shí),foo是兩個(gè)操作數(shù)的確切值并不足以保證在產(chǎn)生的匯編代碼中它們會出現(xiàn)在相同的位置。下面的代碼是不可靠的:
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
各種優(yōu)化或重新裝載可以使操作數(shù)0和1在不同的寄存器中;GCC 知道沒有理由不這樣做。舉例來說,編譯器可能會找到一個(gè)寄存器中foo值得拷貝并用它作為操作數(shù)1,但是產(chǎn)生的輸出操作數(shù)0卻在另外一個(gè)寄存器中(后來拷 貝到foo自己的地址里)。當(dāng)然,由于用于操作數(shù)1的寄存器在匯編碼中甚至沒有被提及,就不會產(chǎn)生結(jié)果。但GCC卻不能指出來。
一些頻繁使用的指令指定了硬設(shè)備寄存器。為了描述這些,在輸入操作數(shù)之后寫上第三個(gè)冒號,后面緊跟著頻繁使用的硬設(shè)備寄存器的名字(以串的形式給出)。這又一個(gè)現(xiàn)實(shí)的例子:asm volatile ("movc3 %0,%1,%2" : /* no outputs */ : "g" (from), "g" (to), "g" (count) : "r0", "r1", "r2", "r3", "r4", "r5");
你不可能通過用一個(gè)輸入操作數(shù)或一個(gè)輸出操作數(shù)交迭的方式來描述一個(gè)頻繁使用的硬設(shè)備寄存器。舉個(gè)例子,如果你在快表中提到一個(gè)寄存器,你就不可能 用一個(gè)操作數(shù)描述一個(gè)有一個(gè)成員的寄存器組。你沒有辦法去指定一個(gè)輸入操作數(shù)沒有同時(shí)指定為輸出操作數(shù)時(shí)被修正。注意如果你指定所有輸出操作數(shù)都出于這個(gè) 目的(而且因此沒有被使用),你就需要去指定可變的匯編代碼構(gòu)造,像下面說得那樣,去阻止GCC刪除那些沒有被用到的匯編代碼段。 如果你從匯編代碼中找到一個(gè)特殊的寄存器,你大概不得不在第三個(gè)冒號列出之后這個(gè)寄存器來告訴編譯器寄存器的值是修正值。在一些匯編程序中,寄存器的名字以`%'開始;要在匯編代碼中生成一個(gè)‘%’,你必須在輸入時(shí)這樣寫:`%%'。
如果你的匯編指令可以改變條件碼寄存器,在頻繁使用的寄存器列表中加上`cc'。GCC在一些機(jī)器上表現(xiàn)條件碼像制定硬設(shè)備寄存器一樣;`cc'可以去命名這種寄存器。在其它機(jī)器上。條件碼被給于不同的操作,而且指定`cc'沒有效果。但是它在任何機(jī)器上總是有效的。
如果你的匯編指令通過不可預(yù)知的方式修正存儲器,在頻繁使用的寄存器列表中加上`memory'。這會使GCC通過匯編指令不把存儲器的值存入寄存器。如果存儲器沒有受影響,你也可以加上可變的沒有被列在匯編程序輸入輸出表上的關(guān)鍵字,就像`memory'的頻繁使用沒有被計(jì)算反而成為匯編程序的副作用一樣。
你可以在一個(gè)匯編模塊中把多個(gè)匯編指令放在一起,用系統(tǒng)中通常用的字符將它們隔開。在大多數(shù)地方一個(gè)聯(lián)合是新的一行打斷原來的行,加上一個(gè)空格鍵移 動到指令區(qū)域(象這樣寫 /n/t)。如果匯編程序允許將逗號作為斷行字符的話,逗號是可以使用的。注意,一些匯編程序語法將逗號作為注釋的開始。輸入操作數(shù)被保證不被用于任何頻 繁使用的寄存器和輸出操作數(shù)的地址,所以你可以隨意讀寫頻繁使用的寄存器。以下是一個(gè)模塊中多重指令的例子;它假定子程序 _foo從寄存器9和10上接受參數(shù):
asm ("movl %0,r9/n/tmovl %1,r10/n/tcall _foo" : /* no outputs */ : "g" (from), "g" (to) : "r9", "r10");
除非一個(gè)輸出操作數(shù)有`&'修正限制,否則GCC會把它當(dāng)作一個(gè)不相干的操作數(shù)而分配給它一個(gè)相同的寄存器,而這是建立在輸出產(chǎn)生之前輸入 被銷毀的假定之上。當(dāng)匯編代碼多于一條指令時(shí),這個(gè)假定就可能有錯(cuò)。這種情況下,對每個(gè)輸出操作數(shù)用`&'可能不會和一個(gè)輸入交迭。參見 20.7.4修正限制字符。
如果你想測試一下由匯編指令產(chǎn)生的代碼情況,你必須包含一個(gè)象下面這樣的分支和標(biāo)志和匯編構(gòu)造:
asm ("clr %0/n/tfrob %1/n/tbeq 0f/n/tmov #1,%0/n0:" : "g" (result) : "g" (input));
這個(gè)要假定你的匯編程序支持局部標(biāo)簽,象GNU匯編器和大多數(shù)UNIX匯編器做的那樣。
談到標(biāo)簽,不允許從一個(gè)匯編程序跳到另一個(gè),編譯器優(yōu)化者不知道這樣的跳轉(zhuǎn),所以當(dāng)它們決定怎樣去優(yōu)化時(shí)不會考慮這些。
用匯編指令通常最方便的方法是把指令壓縮在一個(gè)象宏定義的函數(shù)里。舉個(gè)例子:
#define sin(x) ({ double __value, __arg = (x); asm ("fsinx %1,%0": "=f" (__value): "f" (__arg)); __value; })
這里用可變的__arg來保證指令在合適的double型特征值上運(yùn)行,而且僅僅接受這些能自動轉(zhuǎn)換為double的參數(shù)。
另外一個(gè)可以確保指令在正確的數(shù)據(jù)類型上運(yùn)行的方法是在匯編程序中用一張表。這和用可變__arg指出在于它可以轉(zhuǎn)化為更多的類型。舉例來說:你想得到的類型是整型,整形參數(shù)會接受一個(gè)指針而不會出錯(cuò),當(dāng)你把這個(gè)參數(shù)傳給一個(gè)名字為__arg的整型變量時(shí),就會出現(xiàn)使用指針的警告,除非你再調(diào)用函數(shù)中明確轉(zhuǎn)化它。
如果一個(gè)匯編程序已經(jīng)有了輸出操作數(shù),為了優(yōu)化GCC會假定指令對改變輸出操作數(shù)沒有副作用。這并不意味著有副作用的指令不會被用到,但你必須要小 心,編譯器會略去沒有被使用的輸出操作數(shù)或使他們移出循環(huán),如果他們組成一個(gè)表達(dá)式時(shí),會用一個(gè)代替兩個(gè)。同時(shí)當(dāng)你的指令在一個(gè)看上去不會改變的變量上沒 有副作用,如果它被在一個(gè)寄存器中找到的話,舊的值在不久之后會被再次使用。
通過在匯編程序后寫上可變關(guān)鍵字,你可以阻止一條匯編指令被刪除,永久被移走,或被結(jié)合起來。例:
#define get_and_set_priority(new) ({ int __old; asm volatile ("get_and_set_priority %0, %1" : "=g" (__old) : "g" (new)); __old; })
如果你寫的匯編指令沒有輸出,GCC會知道這條指令有副作用而不會刪除它或者把它移到循環(huán)外。
可變關(guān)鍵字表示指令有很大的副作用。GCC不會刪除一段匯編程序如果它是可達(dá)的話。(指令仍會被刪除如果GCC不能證明控制流會到達(dá)指令。)GCC對一些可變的匯編指令不會重新排序。例:
*(volatile int *)addr = foo; asm volatile ("eieio" : : );
假定addr包含一個(gè)映射到設(shè)備寄存器的內(nèi)存地址。個(gè)人微機(jī)的eieio(強(qiáng)迫輸入輸出執(zhí)行順序)指令會告訴CPU要保證在其它輸入輸出之前要執(zhí)行設(shè)備寄存器中所保存的指令。
注意對編譯器來說即使是一個(gè)看上去無關(guān)緊要的可變匯編指令也能被移動,比如通過jump指令。你不要期盼一個(gè)可變匯編指令的順序會保持非常的連貫。 如果你需要連貫的輸出,就用單一的匯編程序吧。GCC還會通過一條可變匯編指令來進(jìn)行一些優(yōu)化;GCC不會像其它編譯器一樣在遇到可變匯編指令時(shí)忘記每一 件事。
沒有任何操作數(shù)的匯編指令會像一個(gè)可變匯編指令來同等對待。
準(zhǔn)許訪問匯編程序指令條件碼是很自然的想法。然而,但我們試圖去實(shí)現(xiàn)這個(gè)時(shí),卻沒有可靠的辦法。問題在于輸出操作數(shù)可能會被再次裝載,這可能會導(dǎo)致 額外增加的儲存指令。在大多數(shù)機(jī)器上,這些指令會在測試時(shí)間改變條件碼。這些問題不會出現(xiàn)在普通的測試和比較指令上。因?yàn)樗麄儧]有任何輸出操作數(shù)。
由于和上面提到的相似的原因,讓一個(gè)匯編指令有權(quán)訪問先前指令留下的條件碼是不可能的。如果你要寫一個(gè)被包含在標(biāo)準(zhǔn)C程序中的頭文件時(shí),用__asm__ 代替asm。參見5.39 備用關(guān)鍵字
5.36.1 i386浮點(diǎn)數(shù)匯編操作數(shù)
在匯編操作數(shù)使用規(guī)則中有很多是站寄存器的用法。這些規(guī)則只對棧寄存器中的操作數(shù)起作用: 在匯編程序中給一組死的操作數(shù),就需要知道那些在匯編時(shí)被暗自取出,而在GCC中必須是被明確彈出的。一個(gè)在匯編時(shí)會被暗自彈出的輸入寄存器必須被明確被標(biāo)志為頻繁使用的,除非你強(qiáng)制它去匹配一個(gè)輸出操作數(shù)。
對任何在匯編時(shí)會被暗自彈出的輸入寄存器,就需要知道怎樣去調(diào)解一個(gè)堆棧進(jìn)行彈出補(bǔ)償。如果棧里任何沒有彈出的輸入比任何彈出寄 存器更靠近棧頂,就很難知道棧是什么樣子,剩下的棧的去向也就很難知道了。任何會被彈出的操作數(shù)都要比不被彈出的操作數(shù)更靠近棧頂。如果一個(gè)輸入死在an insn,對輸出重新裝載可能會用到輸入寄存器。看看下面這個(gè)例子:
asm ("foo" : "=t" (a) : "f" (b));
這行匯編程序的意思是輸入的B不會在匯編時(shí)被彈出,匯編器會把結(jié)果壓入棧寄存器,棧會比壓入之前更深一層。但是,如果B死在這insn里,重新裝載對輸入和輸出用同一個(gè)寄存器是有可能的。
如果任何輸入操作數(shù)用到了f約束, 所有的輸出寄存器限制必須用到& earlyclobber。上面的匯編程序可以寫成這樣: asm ("foo" : "=&t" (a) : "f" (b));
一些操作數(shù)需要放在棧的特殊位置。
由于所有的387操作碼用到了讀寫操作數(shù),在匯編操作數(shù)之前的輸出操作數(shù)都是死的,而且會被匯編操作數(shù)壓棧。除了棧頂之外壓在任何地方是沒有意義的。
輸出操作數(shù)必須從棧頂開始:而且不可能是一個(gè)寄存器跳躍。
一些匯編程序段可能需要額外的棧空間用于內(nèi)部計(jì)算。這可以用式輸入輸出與棧寄存器不相干的方法來保證。 這里有幾個(gè)要去寫的匯編代碼。這段匯編接收一個(gè)可以在內(nèi)部彈出的輸入,并產(chǎn)生兩個(gè)輸出。
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");這段匯編接收兩個(gè)通過操作碼fyl2xp1彈出的輸入,并產(chǎn)生一個(gè)輸出代替它們。用戶必須給出用于棧寄存器頻繁使用的st(1)的C代碼來了解fyl2xp1 彈出兩個(gè)輸入。
5.37匯編代碼中使用的控制名字
在進(jìn)行聲明之后你可以為C函數(shù)或函數(shù)變元的匯編代碼中寫入asm (or __asm__)來指定你要在匯編代碼中使用的名字,象下面這樣: int foo asm ("myfoo") = 2;
這種用于在匯編代碼中用于變量foo指定應(yīng)該是myfoo' 而不是通常的 `_foo'.
系統(tǒng)中的C函數(shù)或變量通常是以下劃線開始的,這種特征就不允許你以下劃線開始為一個(gè)連接命名。
非靜態(tài)局部變量對這種特征是沒有意義的,因?yàn)檫@種變量不需要由匯編程序名。如果你試著把一個(gè)變量放在一個(gè)指定的寄存器中,看看5.38節(jié) 指定寄存器中的變量。GCC目前把這種代碼當(dāng)作一種警告, 但是在不久后它就可能變成一個(gè)錯(cuò)誤,而不僅僅是警告了。
你不能在函數(shù)定義時(shí)這樣用匯編;但是你可以像這樣,在定義前為函數(shù)寫一個(gè)聲明并且把匯編代碼放在這里:
extern func () asm ("FUNC"); func (x, y) int x, y; ...
確保匯編程序的名字不會與其它任何匯編標(biāo)記發(fā)生沖突。而且,不能使用寄存器名;那會產(chǎn)生完全無效的匯編代碼。GCC目前還沒有能力在寄存器中存儲這些靜態(tài)變量,以后或許會被架上。
5.38 指定寄存器中的變量
GNU C允許你把商量的全局變量放在指定的硬設(shè)備寄存器中。你還可以在為普通變量分配的寄存器中指定這樣的寄存器。 全局寄存器變量通過程序保留寄存器。這在編程中非常有用,比如有多個(gè)會被頻繁訪問全局變量的編程語言解釋程序。 指定寄存器中的局部寄存器變量不需要保有寄存器。編譯器的數(shù)據(jù)流分析有能力決定那里指定的寄存器包含活的值,這些變量在哪里會被其它代碼用到。在數(shù)據(jù)流分析中表現(xiàn)為死的寄存器變量可能會被刪除。提到寄存器變量可能會被刪除、移動或是簡化。 通過擴(kuò)展匯編的特征局部變量有時(shí)使用起來很方便(參見5.36 匯編指令和C表達(dá)式 操作數(shù)),如果你要用匯編指令直接在指定寄存器中寫入輸出值的話。(倘若你指定的寄存器適合匯編里操作數(shù)的指定限制的話)。
5.38.1 定義全局寄存器變量
GCC中你可以這樣定義全局寄存器變量: register int *foo asm ("a5");
這里的a5是要使用的寄存器名。在你的機(jī)器上選一個(gè)通常被保留或是被函數(shù)調(diào)用釋放的寄存器,以使庫函數(shù)不能頻繁使用它。
通常來說寄存器名是由CPU決定的,所以你可能需要根據(jù)CPU類型調(diào)整你的程序。在68000上寄存器a5是一個(gè)好的選擇。在有寄存器窗口的機(jī)器上,一定要選擇一個(gè)不會受函數(shù)調(diào)用機(jī)制影響的全局變量寄存器。
另外,運(yùn)行在同種CPU上的操作系統(tǒng)可能會在寄存器命名上有區(qū)別;這樣你就需要更多的條件。舉例來說,一些68000操作系統(tǒng)把這個(gè)寄存器叫做%a5。
最終有了一個(gè)可以讓編譯器自動選擇寄存器的方法,但首先我們要決定編譯器怎樣去選并讓你去引導(dǎo)這個(gè)選擇。顯然沒有這種辦法。
在特定寄存器上定義一個(gè)全局寄存器變量,要保證寄存器完全用作這個(gè)用途,至少在當(dāng)前時(shí)這樣實(shí)現(xiàn)的。這些寄存器不會被函數(shù)存儲起來。存儲這些寄存器永遠(yuǎn)不會被刪除即使它表現(xiàn)為死,但是提到寄存器變量可能會被刪除、移動或是簡化。 從信號或多個(gè)控制線程操作這些全局變量是不安全的,因?yàn)橄到y(tǒng)庫函數(shù)可能會為其它事用到這些寄存器(除非你為了手頭的工作特別重新編譯它們)。
一個(gè)函數(shù)通過第三個(gè)不知道這個(gè)全局變量的函數(shù)(也就是說,在不同的源文件中這個(gè)變量沒有被聲明),用這個(gè)全局變量去調(diào)用另一個(gè)foo這樣的函數(shù)是不安全的。這是由于損失可能保有的這個(gè)寄存器并把其它變量放在那里。舉例來說,你不能指望一個(gè)你要傳遞給qsort的全局變量在比較函數(shù)中被用到,因?yàn)閝sort可能會把其它的什么東西放在那個(gè)寄存器中。(如果你用相同的全局變量重新編譯qsort,問題就會得到解決)
如果你要重新編譯qsort或是世上沒有用到你的全局寄存器變量的源文件,已達(dá)到它們不使用那個(gè)寄存器的目的,接下來它就有能力指定編譯器選項(xiàng)`-ffixed-reg'。事實(shí)上你不需要為那些源文件增加一個(gè)全局變量寄存器聲明。
編譯時(shí)沒有這個(gè)全局寄存器變量的函數(shù)去調(diào)用一個(gè)可以改變?nèi)旨拇嫫髯兞康暮瘮?shù)是安全的,因?yàn)樗梢越?jīng)常使調(diào)用者期待在那里找到用來返回的值。因此,用到全局變量的程序的入口函數(shù)必須被明確保存,并且要恢復(fù)屬于調(diào)用者的值。
在大多數(shù)機(jī)器上,在設(shè)置跳躍的時(shí)候,遠(yuǎn)跳躍會恢復(fù)每一個(gè)全局寄存器變量的值。而在另一些機(jī)器上,遠(yuǎn)跳躍不會改變這些值??紤]到移植性,調(diào)用設(shè)置跳躍指令的函數(shù)必須用其它參數(shù)儲存這些全局寄存器變量的值,并在遠(yuǎn)跳躍時(shí)恢復(fù)他們。這樣,無論遠(yuǎn)跳躍做什么都有同樣的效果。
所有的全局寄存器變量聲明必須在函數(shù)定義之前。否則,那些寄存器可能在先前的函數(shù)中被使用,而聲名阻止這樣卻晚了。
全局寄存器變量不應(yīng)該有初始值,因?yàn)橐粋€(gè)可執(zhí)行文件并不意味著會為寄存器提供初始值。
在sun工作站上,g3 ... g7被認(rèn)為是合適的寄存器,但是某幾個(gè)庫函數(shù),像getwd,用于分割和求余的子程序,修正了g3 和 g4,g1 和 g2 是局部臨時(shí)存儲器。
在68000上,a2 ... a5 d2 ... d7應(yīng)該是合適的,當(dāng)然,用很多這樣的寄存器就不合適了。
5.38.2 用于局部變量的指定寄存器
你可以用一個(gè)制定的寄存器定義一個(gè)局部寄存器變量: register int *foo asm ("a5");
這里的a5是要使用的寄存器名。注意這和定義全局寄存器變量的語法是相同的,只是局部寄存器變量要出現(xiàn)在一個(gè)函數(shù)中。
通常來說寄存器名是由CPU決定的,但這并不是問題,因?yàn)橹付拇嫫鞔蠖嘤迷谕庠诘膮R編指令上(參見5.36 匯編指令和C表達(dá)式 操作數(shù))。所有這些通常都需要你根據(jù)CPU的類型給你的程序加上條件。
另外,運(yùn)行在同種CPU上的操作系統(tǒng)可能會在寄存器命名上有區(qū)別;這樣你就需要更多的條件。舉例來說,一些68000操作系統(tǒng)把這個(gè)寄存器叫做%a5。
定義這樣一個(gè)局部寄存器變量,不需要保有這個(gè)寄存器;它留下的其它代碼可用的部分放在控制流可以決定變量的值不是活著的地方。然而,這些寄存器在重新裝載途徑是不可用的;過多地使用這些特征會使編譯器在編譯特定程序時(shí)沒有可用的寄存器。
不是任何時(shí)候這些選項(xiàng)都能保證GCC能夠產(chǎn)生寄存器中包含這種變量的代碼。在一個(gè)匯編聲明中你不必要為這個(gè)寄存器寫一段外在的說明并假定它總是會提及這個(gè)變量。 當(dāng)變量的值在數(shù)據(jù)流分析中表現(xiàn)為死的時(shí)候,存儲在變量中的值可能會被刪除。提到寄存器變量可能會被刪除、移動或是簡化。
5.39 備用關(guān)鍵字
用選項(xiàng)`-traditional'可以使一些關(guān)鍵字失去作用;`-ansi' 和各種 `-std'選項(xiàng)是另外一些失效。在一個(gè)被所有程序(指包含標(biāo)C和傳統(tǒng)C)時(shí)都要使用的多種功能的頭文件中,當(dāng)你要用GNU擴(kuò)展C或標(biāo)準(zhǔn)C時(shí),就會產(chǎn)生麻煩。在編譯程序是加上選項(xiàng)`-ansi'時(shí),關(guān)鍵字asm, typeof 和 inline會失效(盡管inline可以和`-std=c99'用在程序編譯中),同時(shí)在編譯程序是加上選項(xiàng)`-traditional'時(shí),關(guān)鍵字const, volatile, signed, typeof 和 inline會失效。標(biāo)準(zhǔn)C99里限制的關(guān)鍵字只有當(dāng)`-std=gnu99'(被最終默認(rèn))或者是`-std=c99'(等價(jià)于 `-std=iso9899:1999') 被使用時(shí)才會有效。 解決這個(gè)問題的方法是在每個(gè)有爭議的關(guān)鍵字的首部和尾部加上`__'。例如:用 __asm__ 代替 asm, __const__ 代替 const, __inline__ 代替 inline.
其它編譯器不會接受這種有選擇性的關(guān)鍵字;
如果你要有其它的編譯器編譯,你可以通過宏用習(xí)慣的關(guān)鍵字去替代備用的關(guān)鍵字。像這樣:
#ifndef __GNUC__ #define __asm__ asm #endif
`-pedantic'和其它的一些選項(xiàng)對許多GNU C擴(kuò)展會產(chǎn)生警告。你可以通過在一個(gè)表達(dá)式前寫上__extension__來阻止這種警告。這樣用__extension__ 不會有副作用。
5.40 不完整的枚舉類型
你可以不用指定它的可能值定義一個(gè)枚舉標(biāo)記。這樣會產(chǎn)生一個(gè)不完整的類型,很像你寫了一個(gè)結(jié)構(gòu)體foo而沒有描述它的元素所得到的東西。一個(gè)稍后的聲明可以指定可能的值來完善這個(gè)類型。 你不能給不完善的類型分配存儲空間,或使它成為變量。然而,你可以用指針指向它。
這個(gè)擴(kuò)展或許不是非常有用,但它是枚舉類型的操作和對結(jié)構(gòu)體和共用體的操作更加一致。 GNU C++不支持這個(gè)擴(kuò)展。
5.41 用字符串命名函數(shù)
GCC預(yù)先假定兩個(gè)魔術(shù)標(biāo)識符用來保存當(dāng)前函數(shù)名。當(dāng)函數(shù)名出現(xiàn)在源文件中時(shí),函數(shù)名會被保存在標(biāo)識符__FUNCTION__中。標(biāo)識符__PRETTY_FUNCTION__保存著在一個(gè)語言的特殊模式中打印出來很漂亮的函數(shù)名字。 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)
編譯器會自動用文字傳中含有合適名字的字符串替代標(biāo)志服。這樣,他們就不會預(yù)處理宏和變量,如__FILE__ 和 __LINE__。這就意味著它們和其它的字符串是由連接關(guān)系的,而且它們可以用來初始化字符數(shù)組。比如: char here[] = "Function " __FUNCTION__ " in " __FILE__;
另一方面,`#ifdef __FUNCTION__' 在函數(shù)中沒有任何特殊的意義,因?yàn)轭A(yù)編譯程序不會對標(biāo)識符__FUNCTION__做任何專門的事。
GCC仍然支持C99標(biāo)準(zhǔn)里定義的魔術(shù)字__func__。
標(biāo)志符被編譯者含蓄的聲明了就好像發(fā)表聲明,立即執(zhí)行每個(gè)函數(shù)定義的下一個(gè)操作句柄,函數(shù)名是裝入詞匯表函數(shù)。這個(gè)名字是原始的函數(shù)名。
static const char __func__[] = "function-name";
這樣定義時(shí),__func__是一個(gè)變量而不是一個(gè)字符串。特別指出,__func__不能連接其它字符串。
C++中,和聲明__func__一樣,__func__和__PRETTY_func__都是變量。
5.42 獲取函數(shù)返回值或結(jié)構(gòu)地址
這些函數(shù)可以被用來獲取函數(shù)中調(diào)用者的信息。 內(nèi)置函數(shù): void * __builtin_return_address (unsigned int level)
這個(gè)函數(shù)返回了當(dāng)前函數(shù)的返回地址,或是它的主調(diào)函數(shù)地址。這里的等級參數(shù)是一個(gè)用于掃描調(diào)用棧的結(jié)構(gòu)數(shù)字。如果這個(gè)值是0的話,返回的是當(dāng)前函數(shù) 返回地址;如果是1的話,返回的是當(dāng)前函數(shù)的主調(diào)函數(shù)的返回地址,等等。等級參數(shù)必須是一個(gè)整型常量。在一些機(jī)器上,不可能決定除了當(dāng)前函數(shù)外其它函數(shù)的 返回地址。在這種情況下,或者是沒有到達(dá)棧頂,這個(gè)函數(shù)會返回0。
這個(gè)函數(shù)只能和非零參數(shù)一起用作調(diào)試。
內(nèi)置函數(shù): void * __builtin_frame_address (unsigned int level)
這個(gè)函數(shù)和__builtin_return_address很相似,但他返回的是函數(shù)結(jié)構(gòu)而不是函數(shù)地址。用0值調(diào)用這個(gè)函數(shù)時(shí)返回當(dāng)前函數(shù)的結(jié)構(gòu)地址,用1值調(diào)用這個(gè)函數(shù)時(shí)返回當(dāng)前函數(shù)的主調(diào)函數(shù)的結(jié)構(gòu)地址,等等。
這里的結(jié)構(gòu)是堆棧中保存局部變量和寄存器的一塊區(qū)域。結(jié)構(gòu)地址通常是第一個(gè)被這個(gè)函數(shù)壓入堆棧的第一個(gè)字的地址。然而,精確的定義取決于處理器和調(diào)用協(xié)定。如果處理器有一個(gè)專門的結(jié)構(gòu)指針寄存器,并且函數(shù)有一個(gè)這樣的結(jié)構(gòu),函數(shù)會返回這個(gè)結(jié)構(gòu)指針寄存器的值。用于函數(shù)__builtin_return_address中的警告同樣適用于這個(gè)函數(shù)。
5.43 GCC提供的其它內(nèi)置函數(shù)
GCC提供了大量的內(nèi)置函數(shù)。其中一些只在處理例外或變量長度參數(shù)表時(shí)內(nèi)部使用。之所以沒有列出,是由于它們可能隨時(shí)間變化;一般情況下,我們不推薦使用這些函數(shù)。 保留的函數(shù)是為優(yōu)化而提供的。
GCC包含了許多標(biāo)準(zhǔn)C庫函數(shù)的內(nèi)置版本。加了前綴的版本會被當(dāng)作C庫函數(shù)即使你加了`-fno-builtin'選項(xiàng)。(參見3.4 C語法控制選項(xiàng))。許多這樣的函數(shù)只有在特定情況下才會被優(yōu)化;如果在某種特例下沒有被優(yōu)化,這個(gè)函數(shù)就會去調(diào)用庫函數(shù)。
這些函數(shù)會被識別并假定沒有返回abort, exit, _Exit 和 _exit,但里一方面卻不是內(nèi)置的。在嚴(yán)格的國際標(biāo)準(zhǔn)C模式下(使用了`-ansi', `-std=c89' 或 `-std=c99')沒有_exit。在嚴(yán)格的C89模式下(使用了`-ansi' 或 `-std=c89')沒有_Exit。
在嚴(yán)格的ISO C模式外,函數(shù)alloca, bcmp, bzero, index, rindex 和 ffs被當(dāng)作內(nèi)置函數(shù)處理。相應(yīng)的版本__builtin_alloca, __builtin_bcmp, __builtin_bzero, __builtin_index, __builtin_rindex 和 __builtin_ffs 會在嚴(yán)格的國際標(biāo)準(zhǔn)C模式下被識別。
ISO C99函數(shù)conj, conjf, conjl, creal, crealf, creall, cimag, cimagf, cimagl, llabs 和 imaxabs 除了在嚴(yán)格的C89模式下都被當(dāng)作內(nèi)置函數(shù)。在國際標(biāo)準(zhǔn)C99模式下的函數(shù)cosf, cosl, fabsf, fabsl, sinf, sinl, sqrtf, 和 sqrtl的內(nèi)置版本在任何模式都可被識別,這是由于為了在標(biāo)準(zhǔn)C99下提出使用它們,C89保留了這些函數(shù)名字。所有這些函數(shù)的相應(yīng)版本前都加了__builtin_前綴。
除非指定選項(xiàng)`-fno-builtin',否則這些C89中的函數(shù)會被當(dāng)作內(nèi)置函數(shù)加以識別: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。所有這些函數(shù)的相應(yīng)版本前都加了__builtin_前綴。
GCC提供了國際C99標(biāo)準(zhǔn)的浮點(diǎn)數(shù)比較宏的版本(可以避免提高無序操作數(shù)的例外):__builtin_isgreater, __builtin_isgreaterequal, __builtin_isless, __builtin_islessequal, __builtin_islessgreater, 和 __builtin_isunordered。
內(nèi)置函數(shù):int __builtin_constant_p (exp)
你可以用內(nèi)置函數(shù)__builtin_constant_p來決定在編譯時(shí)是否一個(gè)值是常數(shù),以此GCC可以執(zhí)行含有那個(gè)常數(shù)的常量疊加表達(dá)式。這個(gè)函數(shù)的參數(shù)是要測試的值。如果那個(gè)值在編譯時(shí)是常量,函數(shù)返回整數(shù)1,否則返回0。返回0并不表示那個(gè)值不是常數(shù),不過通過給定`-O'選項(xiàng),GCC不能證明它是一個(gè)常量。
這個(gè)函數(shù)的一個(gè)有代表性的用途是內(nèi)存成為臨界資源時(shí)的深入應(yīng)用。假設(shè)你有許多復(fù)雜的運(yùn)算,如果這里面包含常數(shù)的話,你可能需要它被打包。沒有的話,就需要去調(diào)用一個(gè)函數(shù)。例如:
#define Scale_Value(X) (__builtin_constant_p (X) ? ((X) * SCALE + OFFSET) : Scale (X))
你可以在一個(gè)宏或是內(nèi)聯(lián)函數(shù)中使用這個(gè)內(nèi)置函數(shù)。然而,如果你在一個(gè)內(nèi)聯(lián)函數(shù)中用到它并把這個(gè)函數(shù)的參數(shù)傳給內(nèi)置函數(shù),當(dāng)你通過一個(gè)字符常量或復(fù)合字(參見5.21 復(fù)合文字)調(diào)用這個(gè)內(nèi)聯(lián)函數(shù)時(shí),GCC不會返回1;除非你指定選項(xiàng)`-O' ,否則當(dāng)你給內(nèi)聯(lián)函數(shù)傳遞一個(gè)數(shù)字常量時(shí),GCC不會返回1。
你還可以用__builtin_constant_p初始化靜態(tài)數(shù)據(jù)。例如:
static const int table[] = { __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1, /* ... */ };
即使EXPRESSION不是一個(gè)常量表達(dá)式,這樣的初始化也是可以接受的。在這種情況下,GCC應(yīng)該在評估內(nèi)置函數(shù)時(shí)更加保守,因?yàn)檫@沒有機(jī)會去優(yōu)化。
先前的GCC版本不接受這樣的數(shù)據(jù)初始化內(nèi)置函數(shù)。最早的完全可以支持的版本是3.0.1。
內(nèi)置函數(shù):long __builtin_expect (long exp, long c)
使用__builtin_expect 是為編譯器提供分支預(yù)測信息??偟膩碚f,你應(yīng)該更喜歡真實(shí)地反饋(通過`-fprofile-arcs'),因?yàn)槌绦騿T在預(yù)言他們的程序執(zhí)行時(shí)聲名不怎么好。然而,在有些應(yīng)用程序中收集這樣的信息是很難的。
返回值是exp的值,exp應(yīng)該是一個(gè)整型表達(dá)式。c的值在編譯時(shí)必須是一個(gè)常數(shù)。內(nèi)置函數(shù)的語法期望exp == c.例如:
if (__builtin_expect (x, 0)) foo ();
必須指出,我們并不指望調(diào)用foo,因?yàn)槲覀兿M鹸的值是0。限于exp必須是整形表達(dá)式,當(dāng)測試指針或浮點(diǎn)時(shí)應(yīng)使用這樣的結(jié)構(gòu):
if (__builtin_expect (ptr != NULL, 1)) error ();
|
|