當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > C語(yǔ)言與CPP編程
[導(dǎo)讀]從C11開始,標(biāo)準(zhǔn)引入了一個(gè)新概念“屬性(attribute)”,本文將簡(jiǎn)單介紹一下目前在C標(biāo)準(zhǔn)中已經(jīng)添加的各個(gè)屬性以及常用屬性的具體應(yīng)用。?一?屬性(Attribute)的前世今生其實(shí)C早在[pre03]甚至更早的時(shí)候就已經(jīng)有了屬性的需求。彼時(shí),當(dāng)程序員需要和編譯器溝通,為某些...


從C 11開始,標(biāo)準(zhǔn)引入了一個(gè)新概念“屬性(attribute)”,本文將簡(jiǎn)單介紹一下目前在C 標(biāo)準(zhǔn)中已經(jīng)添加的各個(gè)屬性以及常用屬性的具體應(yīng)用。?
一? 屬性(Attribute)的前世今生
其實(shí)C 早在[pre03]甚至更早的時(shí)候就已經(jīng)有了屬性的需求。彼時(shí),當(dāng)程序員需要和編譯器溝通,為某些實(shí)體添加一些額外的信息的時(shí)候,為了避免“發(fā)明”一個(gè)新的關(guān)鍵詞乃至于引起一些語(yǔ)法更改的麻煩,同時(shí)又必須讓這些擴(kuò)展內(nèi)容不至于“污染”標(biāo)準(zhǔn)的命名空間,所以標(biāo)準(zhǔn)保留了一個(gè)特殊的用戶命名空間——“雙下劃線關(guān)鍵詞”,以方便各大編譯器廠商能夠根據(jù)需要添加相應(yīng)的語(yǔ)言擴(kuò)展。根據(jù)這個(gè)標(biāo)準(zhǔn),各大編譯器廠商都做出了自己的擴(kuò)展實(shí)現(xiàn),目前在業(yè)界廣泛使用的屬性空間有GNU和IBM的 __attribute__(()),微軟的 __declspec(),甚至C#還引入了獨(dú)特的單括號(hào)系統(tǒng)(single bracket system)來(lái)完成相應(yīng)的工作。
隨著編譯器和語(yǔ)言標(biāo)準(zhǔn)的發(fā)展,尤其是C 多年來(lái)也開始逐漸借鑒其他語(yǔ)言中的獨(dú)特?cái)U(kuò)展,屬性相關(guān)的擴(kuò)展也越來(lái)越龐大。但是Attribute的語(yǔ)法強(qiáng)烈依賴于各大編譯器的具體實(shí)現(xiàn),彼此之間并不兼容,甚至部分關(guān)鍵屬性導(dǎo)致了語(yǔ)言的分裂,最終都會(huì)讓使用者的無(wú)所適從。所以在C 11標(biāo)準(zhǔn)中,特意提出了C 語(yǔ)言內(nèi)置的屬性概念。提案大約是在2007年前后形成,2008年9月15日的提案版本n2761被正式接納為C 11標(biāo)準(zhǔn)中的Attribute擴(kuò)展部分(此處歷史略悠久,很可能有不準(zhǔn)確的部分,歡迎各位指正)。
二? 屬性的語(yǔ)法定義
正如我們?cè)谏弦还?jié)討論的,屬性的關(guān)鍵要求就是避免對(duì)標(biāo)準(zhǔn)用戶命名空間的污染,同時(shí)對(duì)于未來(lái)可能引入的更多屬性,我們需要有一個(gè)方式可以避免新加的“屬性關(guān)鍵字”破壞當(dāng)前已有的C 語(yǔ)法。所以新標(biāo)準(zhǔn)采用了“雙方括號(hào)”的語(yǔ)法方式引入了屬性說(shuō)明,比如[[noreturn]]就是一個(gè)標(biāo)準(zhǔn)的C 屬性定義。而未來(lái)新屬性的添加都被控制在雙方括號(hào)范圍之內(nèi),不會(huì)進(jìn)入標(biāo)準(zhǔn)的命名空間。
按照C 語(yǔ)言標(biāo)準(zhǔn),下列語(yǔ)言實(shí)體可以被屬性所定義/并從中獲益:
  • 函數(shù)

  • 變量

  • 函數(shù)或者變量的名稱

  • 類型

  • 程序塊

  • Translation Unit (這個(gè)不知道用中文咋說(shuō))

  • 程序控制聲明


根據(jù)C 的標(biāo)準(zhǔn)提案,屬性可以出現(xiàn)在程序中的幾乎所有的位置。當(dāng)然屬性出現(xiàn)的位置和其修飾的對(duì)象是有一定關(guān)聯(lián)的,屬性僅在合適的位置才能產(chǎn)生效果。比如[[noreturn]必須出現(xiàn)在函數(shù)定義的位置才會(huì)產(chǎn)生效果,如果出現(xiàn)在某個(gè)變量的聲明處則無(wú)效。根據(jù)C 17的標(biāo)準(zhǔn),未實(shí)現(xiàn)的或者無(wú)效的屬性均應(yīng)該被編譯器忽略且不產(chǎn)生任何錯(cuò)誤報(bào)告(在C 17標(biāo)準(zhǔn)之前的編譯器則參考編譯器的具體實(shí)現(xiàn)會(huì)有不同的行為)。
由于屬性可以出現(xiàn)在幾乎所有的位置,那么它是如何關(guān)聯(lián)到具體的作用對(duì)象呢?下面我引用了語(yǔ)言標(biāo)準(zhǔn)提案中的一個(gè)例子幫助大家理解屬性是如何作用于語(yǔ)言的各個(gè)部分。
[[attr1]] class C [[ attr2 ]] { } [[ attr3 ]] c [[ attr4 ]], d [[ attr5 ]];
  • attr1 作用于class C的實(shí)體定義c和d

  • attr2 作用于class C的定義?

  • attr3 作用于類型C?

  • attr4 作用于實(shí)體c?

  • attr5 作用于實(shí)體d??


以上只是一個(gè)基本的例子,具體到實(shí)際的編程中,還有有太多的可能,如有具體情況可以參考C 語(yǔ)言標(biāo)準(zhǔn)或者編譯器的相關(guān)文檔。
三? 主流C 編譯器對(duì)于屬性的支持情況
目前的主流編譯器對(duì)于C 11的支持已經(jīng)相對(duì)很完善了,所以對(duì)于屬性的基本語(yǔ)法,大部分的編譯器都已經(jīng)能夠接納。不過(guò)對(duì)于在不同標(biāo)準(zhǔn)中引入的各個(gè)具體屬性支持則參差不齊,對(duì)于相關(guān)屬性能否發(fā)揮應(yīng)有的作用更需要具體問(wèn)題具體分析。當(dāng)然,在標(biāo)準(zhǔn)中(C 17)也明確了,對(duì)于不支持或者錯(cuò)誤設(shè)定的屬性,編譯器也能夠忽略不會(huì)報(bào)錯(cuò)。
下圖是目前主流編譯器對(duì)于n2761屬性提案的支持情況:



對(duì)于未知或不支持的屬性忽略報(bào)錯(cuò)的主流編譯器支持情況:



四? 目前C 標(biāo)準(zhǔn)中引入的標(biāo)準(zhǔn)屬性
C 11引入標(biāo)準(zhǔn):
  • [[noreturn]]

  • [[carries_dependency]]


C 14引入標(biāo)準(zhǔn):
  • [[deprecated]] 和 [[deprecated("reason")]]


C 17引入標(biāo)準(zhǔn):
  • [[fallthrough]]

  • [[nodiscard]] 和 [[nodiscard("reason")]] (C 20)

  • [[maybe_unused]]


C 20引入標(biāo)準(zhǔn):
  • [[likely]] 和 [[unlikely]]

  • [[no_unique_address]]


接下來(lái)我將嘗試對(duì)已經(jīng)引入標(biāo)準(zhǔn)的屬性進(jìn)行進(jìn)一步的說(shuō)明,同時(shí)對(duì)于已經(jīng)明確得到編譯器支持的屬性,我也會(huì)嘗試用例子進(jìn)行進(jìn)一步的探索,希望拋磚引玉能夠幫大家更好的使用C 屬性這個(gè)“新的老朋友”。
1? [[noreturn]]
從字面意義上來(lái)看,noreturn是非常容易理解的,這個(gè)屬性的含義就是標(biāo)明某個(gè)函數(shù)一定不會(huì)返回。
請(qǐng)看下面的例子程序:
// 正確,函數(shù)將永遠(yuǎn)不會(huì)返回。[[noreturn]] void func1(){?throw?"error";?}
// 錯(cuò)誤,如果用false進(jìn)行調(diào)用,函數(shù)是會(huì)返回的,這時(shí)候會(huì)導(dǎo)致未定義行為。[[noreturn]] void func2(bool b){?if?(b)?throw?"error";?}
int main(){ try {?func1()??;?} catch(char const *e) {?std::cout?<"Got?something:?"?<"??\n";?}
// 此處編譯會(huì)有警告信息。 func2(false);}
這個(gè)屬性最容易被誤解的地方是返回值為void的函數(shù)不代表著不會(huì)返回,它只是沒有返回值而已。所以在例子中的第一個(gè)函數(shù)func1才是正確的無(wú)返回函數(shù)的一個(gè)例子;而func2在參數(shù)值為false的情況下,它還是一個(gè)會(huì)返回的函數(shù)。所以,在編譯的時(shí)候,編譯器會(huì)針對(duì)func2報(bào)告如下錯(cuò)誤:
noreturn.cpp: In function 'void func2(bool)':noreturn.cpp:11:1: warning: 'noreturn' function does return 11 | } | ^
而實(shí)際運(yùn)行的時(shí)候,func2到底會(huì)有什么樣的表現(xiàn)屬于典型的“未定義行為”,程序可能崩潰也可能什么都不發(fā)生,所以一定要避免這種情況在我們的代碼中出現(xiàn)。(我在gcc11編譯器環(huán)境下嘗試過(guò)幾次,情況是什么都不發(fā)生,但是無(wú)法保證這是確定的行為。)
另外,[[noreturn]]只要函數(shù)最終沒有返回都是可以的,比如用exit()調(diào)用直接將程序干掉的程序也是可以被編譯器接受的行為(只是暫時(shí)沒想到為啥要這么干)。
2? [[carries_dependency]]
這個(gè)屬性的作用是允許我們將dependency跨越函數(shù)進(jìn)行傳遞,用于避免在弱一致性模型平臺(tái)上產(chǎn)生不必要的內(nèi)存柵欄導(dǎo)致代碼效率降低。
一般來(lái)說(shuō),這個(gè)屬性是搭配 std::memory_order_consume 來(lái)使用的,支持這個(gè)屬性的編譯器可以根據(jù)屬性的指示生成更合適的代碼幫助程序在線程之間傳遞數(shù)據(jù)。在典型的情況下,如果在 memory_order_consume 的情況下讀取一個(gè)值,編譯器為了保證合適的內(nèi)存讀取順序,可能需要額外的內(nèi)存柵欄協(xié)調(diào)程序行為順序,但是如果加上了[[carries_dependency]]的屬性,則編譯器可以保證函數(shù)體也被擴(kuò)展包含了同樣的dependency,從而不再需要這個(gè)額外的內(nèi)存柵欄。同樣的事情對(duì)于函數(shù)的返回值也是一致的。
參考如下例子代碼:
std::atomic<int *> p;std::atomic<int?*>?q;
void func1(int *val){?std::cout?<std::endl;?}
void func2(int * [[carries_dependency]] val){ q.store(val, std::memory_order_release);std::cout?<std::endl;?}
void thread_job(){ int *ptr1 = (int *)p.load(std::memory_order_consume); // 1 std::cout << *ptr1 << std::endl; // 2 func1(ptr1); // 3 func2(ptr1); // 4}
  • 程序在1的位置因?yàn)閜tr1明確的使用了memory_order_consume的內(nèi)存策略,所以對(duì)于ptr1的訪問(wèn)一定會(huì)被編譯器排到這一行之后。


  • 因?yàn)?的原因,所以這一行在編譯的時(shí)候勢(shì)必會(huì)排列在1后面。


  • func1并沒有帶任何屬性,而他訪問(wèn)了ptr1,那么編譯器為了保證內(nèi)存訪問(wèn)策略被尊重所以必須在func1調(diào)用之間構(gòu)建一個(gè)內(nèi)存柵欄。如果這個(gè)線程被大量的調(diào)用,這個(gè)額外的內(nèi)存柵欄將導(dǎo)致性能損失。


  • 在func2中,我們使用了[[carries_dependency]]屬性,那么同樣的訪問(wèn)ptr1,編譯器就知道程序已經(jīng)處理好了相關(guān)的內(nèi)存訪問(wèn)限制。這個(gè)也正如我們?cè)賔unc2中對(duì)val訪問(wèn)所做的限制是一樣的。那么在func2之前,編譯器就無(wú)需再插入額外的內(nèi)存柵欄,提高了效率。


3? [[deprecated]] 和 [[deprecated("reason")]]
這個(gè)屬性是在C 14的標(biāo)準(zhǔn)中被引入的。被這個(gè)屬性加持的名稱或者實(shí)體在編譯期間會(huì)輸出對(duì)應(yīng)的警告,告訴使用者該名稱或者實(shí)體將在未來(lái)被拋棄。如果指定了具體的"reason",則這個(gè)具體的原因也會(huì)被包含在警告信息中。?
參考如下例子程序:
[[deprecated]]void old_hello() {}
[[deprecated("Use new_greeting() instead. ")]]void old_greeting() {}
int main(){ old_hello(); old_greeting(); return 0;}
支持對(duì)應(yīng)屬性的編譯器上,這個(gè)例子程序是可以通過(guò)編譯并正確運(yùn)行的,但是編譯的過(guò)程中,編譯器會(huì)對(duì)屬性標(biāo)志的函數(shù)進(jìn)行追蹤,并且打印出相應(yīng)的信息(如果定義了的話)。在我的環(huán)境中,編譯程序給出了我如下的提示信息:

deprecated.cpp: In function 'int main()':deprecated.cpp:9:14: warning: 'void old_hello()' is deprecated [-Wdeprecated-declarations] 9 | old_hello(); | ~~~~~~~~~^~deprecated.cpp:2:6: note: declared here 2 | void old_hello() {} | ^~~~~~~~~deprecated.cpp:10:17: warning: 'void old_greeting()' is deprecated: Use new_greeting() instead. [-Wdeprecated-declarations] 10 | old_greeting(); | ~~~~~~~~~~~~^~deprecated.cpp:5:6: note: declared here 5 | void old_greeting() {} | ^~~~~~~~~~~~
[[deprecated]]屬性支持廣泛的名字和實(shí)體,除了函數(shù),它還可以修飾:

  • 類,結(jié)構(gòu)體

  • 靜態(tài)數(shù)據(jù)成員,非靜態(tài)數(shù)據(jù)成員

  • 聯(lián)合體,枚舉,枚舉項(xiàng)

  • 變量,別名,命名空間

  • 模板特化


4? [[fallthrough]]
這個(gè)屬性只可以用于switch語(yǔ)句中,通常在case處理完畢之后需要按照程序設(shè)定的邏輯退出switch塊,通常是添加break語(yǔ)句;或者在某些時(shí)候,程序又需要直接進(jìn)入下一個(gè)case的判斷中。而現(xiàn)代編譯器通常會(huì)檢測(cè)程序邏輯,在前一個(gè)case處理完畢不添加break的情況下發(fā)出一個(gè)警告信息,讓作者確定是否是他的真實(shí)意圖。但是,在case處理部分添加了[[fallthrough]]屬性之后,編譯器就知道這是程序邏輯有意為之,而不再給出提示信息。
5? [[nodiscard]] 和 [[nodiscard("reason")]]
這兩個(gè)屬性和前面的[[deprecated]]類似,但是他們是在不同的C 標(biāo)準(zhǔn)中被引入的,[[nodiscard]]是在C 17標(biāo)準(zhǔn)中引入,而[[nodiscard("reason")]]是在C 20標(biāo)準(zhǔn)中引入。
這個(gè)屬性的含義是明確的告訴編譯器,用此屬性修飾的函數(shù),其返回值(必須是按值返回)不應(yīng)該被丟棄,如果在實(shí)際調(diào)用中舍棄了返回變量,則編譯器會(huì)發(fā)出警示信息。如果此屬性修飾的是枚舉或者類,則在對(duì)應(yīng)函數(shù)返回該類型的時(shí)候也不應(yīng)該丟棄結(jié)果。
參考下面的例子程序:
struct [[nodiscard("IMPORTANT THING")]] important {};important i = important();important get_important() { return i; }important
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉