最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

不是“函數(shù)”的“函數(shù)” - 內(nèi)建函數(shù)

2023-06-24 14:13 作者:冒-_-泡  | 我要投稿

學(xué)過Go語言的同學(xué)一定知道m(xù)ake這個(gè)“內(nèi)建函數(shù)”:

若按官方定義,它的確是一個(gè)“built-in function”,然而,這個(gè)function可不是一般意義的function,具體地說,你要是想自己實(shí)現(xiàn)一個(gè)和它一樣接口的,是做不到的,原因很簡單,因?yàn)榈谝粋€(gè)參數(shù)是type而不是表達(dá)式,且不是反射那種形式

所以make從語言實(shí)現(xiàn)角度說,也可以看做是一個(gè)特殊語法,編譯器識(shí)別出這個(gè)“函數(shù)”后,并不是找到它對(duì)應(yīng)的某個(gè)函數(shù)代碼實(shí)現(xiàn)去調(diào)用(實(shí)際上大概率是沒有的),而是當(dāng)做for、if之類的關(guān)鍵字來處理,走生成代碼的流程

類似的,append、delete之類的內(nèi)建函數(shù)其實(shí)也可看做是做成函數(shù)樣式的語法,例如append:

雖然參數(shù)和返回值都是值,但是在沒有泛型支持的情況下,也沒法直接實(shí)現(xiàn)(當(dāng)然1.18后是可以了),顯然,編譯器在碰到append的時(shí)候是得做一些特殊處理的

作為對(duì)比,某些其他內(nèi)建函數(shù),例如println,從形式上就是一個(gè)普通函數(shù),且自己實(shí)現(xiàn)也沒有什么難度了:

不過由于一些特殊原因,print和println依然被設(shè)計(jì)為內(nèi)建函數(shù),并不是一個(gè)普通函數(shù)

一般而言,我們?cè)谟懻撘婚T高級(jí)語言的語法組織的時(shí)候,可以簡單將其分為“語法+庫”的形式,其中前者可以認(rèn)為是由保留字(即不能用來做變量名的那些關(guān)鍵字,如if、class之類)來實(shí)現(xiàn),由編譯器直接處理,后者則是通過函數(shù)或類或語言支持的其他形式實(shí)現(xiàn)的庫來支持,后者一般被稱為標(biāo)準(zhǔn)庫,雖然標(biāo)準(zhǔn)庫不一定是語言自實(shí)現(xiàn)的(例如有的功能必須用更low level的語言,如匯編來實(shí)現(xiàn)),但從形式上,可以認(rèn)為標(biāo)準(zhǔn)庫和我們自己寫的三方庫在使用上是相等的,或者說,我們完全可以通過自實(shí)現(xiàn)某些標(biāo)準(zhǔn)庫功能,然后替換對(duì)應(yīng)的庫來實(shí)現(xiàn)同等功能,甚至override覆寫相關(guān)功能

比如說你在Python里面就可以這樣搞:

注意,這不是在當(dāng)前namespace寫一個(gè)len來隱藏掉標(biāo)準(zhǔn)的內(nèi)建len,而是直接干掉了標(biāo)準(zhǔn)庫的len,由于Python極度自由的動(dòng)態(tài)性理念,這么干是可以的(當(dāng)然在一個(gè)正式項(xiàng)目中這樣搞就是危險(xiǎn)的)

但是,你沒有辦法去通過合法手段覆蓋保留字實(shí)現(xiàn)的功能,例如變更if語句的執(zhí)行過程之類,這些是語法,而不是庫,從這個(gè)例子也大概可以看到上述的兩層結(jié)構(gòu)的區(qū)別

上面這套二層理論,看著挺完美,層次簡潔,各層功能明確,容易理解且擴(kuò)展性強(qiáng)大,但如開頭說的,Go語言在二者之間搞了內(nèi)建函數(shù)這一層,把情況變復(fù)雜了,其實(shí)不光Go語言,其他一些高級(jí)語言,或編譯器擴(kuò)展,也會(huì)搞出一些類似的東東,這其實(shí)可看做是一種折中方案

例如,為什么不將一些功能(例如上面說的Go的make)做成語法呢,這也許是不想讓語法本身變得過于臃腫,并保留一些靈活性,例如,Go的make依然是一個(gè)可用的標(biāo)識(shí)符:

另外,語言本身是不斷發(fā)展的,發(fā)展過程中會(huì)引入各種新特性,而這些特性的實(shí)現(xiàn)如果隨便采用增加保留字的方式,就很容易造成不兼容的情況,例如想象一下,如果哪天C++規(guī)定“count”成為了保留字,那么會(huì)有多少項(xiàng)目編譯不通過?

所以在發(fā)展問題上,各語言可為煞費(fèi)苦心,C和C++會(huì)告訴你,單下劃線或雙下劃線開頭的標(biāo)識(shí)符不要隨便用,這些有可能被語言自己用到(例如新類型“_Complex”),如果新增語法是“熱門常用”語法,關(guān)鍵字用下劃線開頭過于難看,則也會(huì)盡量使用非保留字的關(guān)鍵字,也就是特殊標(biāo)識(shí)符來實(shí)現(xiàn),例如之前視頻提到過的final和override:

于是在這種情況下,如果要新增語言特性,通過由編譯器介入處理的特殊內(nèi)建函數(shù)來實(shí)現(xiàn)是一個(gè)不錯(cuò)的選擇,它們?cè)谡Z法上是函數(shù),但是在語義上則可能和“函數(shù)調(diào)用”差了十萬八千里,因?yàn)楦緵]有對(duì)應(yīng)的函數(shù)實(shí)現(xiàn),只是一種語法偽裝罷了

例如,用C++寫日志庫的時(shí)候,我們一般需要打印出記錄日志的位置信息,傳統(tǒng)的做法是通過宏來實(shí)現(xiàn):

GNUC擴(kuò)展提供了內(nèi)建函數(shù)來實(shí)現(xiàn)相關(guān)功能:

顯然,這倆函數(shù)大概率是沒有對(duì)應(yīng)的代碼實(shí)現(xiàn),完全就是編譯器對(duì)其做替換罷了,只不過,__FILE__和__LINE__是預(yù)處理器做替換,而這兩個(gè)內(nèi)建函數(shù)是編譯階段做替換,這有什么意義呢?由于語法上是一個(gè)“函數(shù)”,在編譯期處理,所以語法功能上比宏要強(qiáng)一些,例如:

了解C++參數(shù)默認(rèn)值原理的很容易看懂這里為什么能work,用宏是較難做到類似效果了

既然新增語言特性是用函數(shù)形式,那么為什么不直接用庫函數(shù)實(shí)現(xiàn)呢?原因也是很顯然的,因?yàn)檎:瘮?shù)代碼沒法實(shí)現(xiàn),必須編譯器介入

例如,C程序員知道有個(gè)內(nèi)建的“函數(shù)”offsetof:

它可以獲取一個(gè)結(jié)構(gòu)體中某個(gè)成員的相對(duì)偏移,這個(gè)聲明顯然不可能是一個(gè)函數(shù),因?yàn)閭魅雲(yún)?shù)是一個(gè)type和一個(gè)member name,所以文檔中說清楚了,這其實(shí)是一個(gè)宏:“The? macro? offsetof()”

很多資料會(huì)告訴你,這個(gè)宏是這樣實(shí)現(xiàn)的(形式不唯一,但原理差不多):

看上去很巧妙對(duì)不對(duì)?然而我們知道,C和C++語法規(guī)定:“p->q”本質(zhì)上是“(*p).q”,隱含了一個(gè)解引用操作,然后這里對(duì)空指針(假設(shè)空指針值是0)或無效指針(假設(shè)空指針不是0的話,雖然一般也不太可能)解引用,UB了(盡管并沒有去訪問m,但依然是UB)

的確有一些編譯器的libc中是這樣實(shí)現(xiàn)offsetof的,這些編譯器對(duì)于相關(guān)UB也沒有處理,是能work,但是對(duì)于一個(gè)有著很強(qiáng)的優(yōu)化算法,并且追求語法嚴(yán)謹(jǐn)?shù)木幾g器來說,會(huì)怎么實(shí)現(xiàn)呢?看看gcc的操作:

人家直接提供了內(nèi)建函數(shù)來實(shí)現(xiàn)這個(gè)功能(clang也是這樣弄的)

當(dāng)然,對(duì)于上面通過地址0取m偏移的做法,相關(guān)編譯器特殊識(shí)別一下倒也不難

那么編譯器在優(yōu)化的時(shí)候能不能自動(dòng)識(shí)別是不是offsetof?答案是不能直接識(shí)別,因?yàn)楹晏鎿Q是預(yù)處理階段的事情,編譯器要感知這個(gè)就只能用一些間接手段了

類似的另一個(gè)例子,是C語言stdarg.h頭文件中的va_list處理的相關(guān)宏,處理可變長度參數(shù),不少資料會(huì)一本正經(jīng)跟你說,由于函數(shù)參數(shù)是挨個(gè)壓棧,所以這幾個(gè)宏實(shí)際就是根據(jù)指定類型調(diào)整指針偏移來遍歷每個(gè)可變參數(shù)

但是稍稍了解現(xiàn)代編譯器優(yōu)化的就知道這不靠譜,為了效率,函數(shù)傳參很多時(shí)候是通過寄存器組來進(jìn)行的,地址都沒有,談何指針遍歷?

所以GNUC的va arg相關(guān)過程怎么實(shí)現(xiàn)也猜得出來了:

還是得編譯器介入才行,其實(shí)這種做法同時(shí)也屏蔽了下層實(shí)現(xiàn),移植性更好

最后,我們把目光放在那種標(biāo)準(zhǔn)庫中可以用普通方法實(shí)現(xiàn)的函數(shù)身上

前面Python的例子中我們看到,Python標(biāo)準(zhǔn)庫的內(nèi)建函數(shù)是可以被覆蓋替換的,那么其他語言中也是類似的情況嗎

這樣一個(gè)例子(C++):

寫兩個(gè)cpp文件,假裝實(shí)現(xiàn)memset,并將其連接運(yùn)行:

按我的想法gcc的鏈接是從左到右的,libc應(yīng)該是最后一個(gè)被依賴的庫,那么2.cpp中的memset應(yīng)該優(yōu)先被連接進(jìn)來,從而覆蓋掉標(biāo)準(zhǔn)庫的,但結(jié)果并不是這樣

而如果將第一行的string.h的包含去掉,就能打印hahaha了,這至少說明,編譯器在這里是介入了一些操作的,如果查看1.cpp對(duì)應(yīng)的匯編就可以看到,在包含string.h的情況下,編譯器會(huì)認(rèn)為這個(gè)memset就是標(biāo)準(zhǔn)庫的memset,直接將這行改為等價(jià)的a=0

這也就是說,標(biāo)準(zhǔn)庫雖然從形式和理論上是和編譯器分離的庫,但在C這里,它也是語言的一部分,并且是被編譯器的處理流程考慮在內(nèi)的,并不是傻乎乎直接去調(diào)用,即便你偷偷替換掉系統(tǒng)的libc,在上面的例子中也不會(huì)產(chǎn)生任何影響

事實(shí)上,C和C++規(guī)定,對(duì)于標(biāo)準(zhǔn)庫(包括STL)的覆蓋、模板特化、改動(dòng)都是未定義行為,換句話說,編譯器有權(quán)假設(shè)它們的行為就是標(biāo)準(zhǔn)規(guī)定的那樣,從而可以得到更大的優(yōu)化空間,這也是為什么禁止在C++中自行“namespace std”給標(biāo)準(zhǔn)庫添加?xùn)|東的原因。盡管它們被各種資料宣傳為底層語言,但程序員其實(shí)并不自由

不是“函數(shù)”的“函數(shù)” - 內(nèi)建函數(shù)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
德保县| 科尔| 通许县| 日照市| 南雄市| 札达县| 巴彦县| 海林市| 丹阳市| 苏尼特右旗| 买车| 临夏县| 博乐市| 佛坪县| 广汉市| 宜章县| 永安市| 永济市| 屯门区| 沐川县| 平潭县| 广南县| 姜堰市| 彰化县| 卓尼县| 五指山市| 昌平区| 大英县| 尚志市| 稻城县| 庆元县| 深泽县| 新化县| 辽源市| 新津县| 和顺县| 清丰县| 昂仁县| 麻阳| 济宁市| 柞水县|