生動(dòng)形象解釋strict-aliasing規(guī)則的寓言故事一則
有一天,一個(gè)叫做指針的小女孩去參觀了一個(gè)名為“洗佳佳”的神奇工廠,那里生產(chǎn)各種各樣的數(shù)據(jù)。指針很好奇,他想看看工廠里都有什么。進(jìn)門時(shí)一個(gè)自稱“iso管委會(huì)”的工作人員攔住了他,說(shuō): 工廠里的數(shù)據(jù)是有類型之分的,你如果需要訪問(wèn)工廠里某個(gè)類型的數(shù)據(jù),那你就得拿著那個(gè)類型的通行證。指針說(shuō): 我覺(jué)得int類型最常用,我想要申請(qǐng)一個(gè)int通行證。工作人員打了個(gè)電話給管委會(huì)的領(lǐng)導(dǎo),批準(zhǔn)了,于是工作人員拿出一張白卡,拿出int圖章印了上去,說(shuō): 這就是你的通行證了,你只能用它訪問(wèn)int類型的數(shù)據(jù),如果你需要訪問(wèn)其他類型的數(shù)據(jù),請(qǐng)?jiān)俅魏臀覀兟?lián)系,我們會(huì)幫你再做一份不同類型的通行證的。他拿著通行證,上面寫(xiě)著“int”,表示他有訪問(wèn)int類型的數(shù)據(jù)的權(quán)限。 進(jìn)入工廠后,指針在整型車間找到了一個(gè)叫做int的數(shù)據(jù),它是一個(gè)4字節(jié)的整數(shù)。指針用他的int通行證訪問(wèn)了這個(gè)int數(shù)據(jù),沒(méi)有問(wèn)題。我申請(qǐng)的就是int通行證,int通行證訪問(wèn)int類型是完全沒(méi)問(wèn)題的,他心想。 他查閱員工手冊(cè),發(fā)現(xiàn)int通行證不僅可以訪問(wèn)int數(shù)據(jù),也可以訪問(wèn)其他類型的數(shù)據(jù),前提是他必須遵守一個(gè)規(guī)則:strict-aliasing。 strict-aliasing的規(guī)則是這樣的:指針只能訪問(wèn)和他通行證上相同類型的數(shù)據(jù),或者是一個(gè)該類型的超集,比如一個(gè)包含該類型的結(jié)構(gòu)體(struct/class)或聯(lián)合體(union)。如果指針想要訪問(wèn)一個(gè)更小的類型,或者是一個(gè)不同的類型,他必須重新領(lǐng)取一張通行證,而不是對(duì)通行證進(jìn)行涂改,否則他會(huì)被抓住并趕出工廠。例如int通行證不能用于訪問(wèn)short,但是可以訪問(wèn)unsigned int。另外,char和unsigned char是兩個(gè)特例,用任何類型的通行證都可以訪問(wèn)。 指針找到了一個(gè)叫做short的數(shù)據(jù),它是一個(gè)2字節(jié)的整數(shù),指針想用他的int通行證訪問(wèn)這個(gè)short數(shù)據(jù),但被工作人員拒絕了。他覺(jué)得這個(gè)規(guī)則很奇怪,他想知道為什么管委會(huì)要這樣限制他。他接下來(lái)決定去試試看能不能破壞這個(gè)規(guī)則,看看會(huì)發(fā)生什么。 然后,他發(fā)現(xiàn)了一個(gè)門牌上寫(xiě)了char[4]的房間,打開(kāi)一看,里面有4個(gè)搖籃,分別睡著4個(gè)char小孩,呆呆地看著他。指針想要訪問(wèn)這個(gè)數(shù)據(jù),但是他沒(méi)有換通行證,因?yàn)樗X(jué)得這樣太麻煩了。他覺(jué)得反正我的通行證int是4字節(jié)的,用于訪問(wèn)同樣是4字節(jié)的char[4]應(yīng)該沒(méi)問(wèn)題吧?他拿起通行證來(lái)觀察,發(fā)現(xiàn)通行證正面雖然寫(xiě)著“int”,背面卻淡淡寫(xiě)著“char”,他覺(jué)得蹊蹺,查了員工手冊(cè)才知道,原來(lái)洗屁屁工廠的每個(gè)通行證背面都印有char,意味著每一張通行證都可以當(dāng)成char通行證使用。于是他把通行證翻了個(gè)面,當(dāng)成char通行證用,訪問(wèn)了這4個(gè)char數(shù)據(jù),沒(méi)有被發(fā)現(xiàn)。之所以開(kāi)了這個(gè)后門或許是為了方便有時(shí)候需要把數(shù)據(jù)作為字節(jié)處理,例如把網(wǎng)絡(luò)收發(fā)包得到的char緩沖區(qū)當(dāng)成任意符合類型去處理,他心想。 指針很高興,他覺(jué)得自己很聰明。他繼續(xù)尋找其他類型的數(shù)據(jù),試圖用他的通行證訪問(wèn)它們。他找到了一個(gè)叫做unsigned int的數(shù)據(jù),它是一個(gè)四字節(jié)的無(wú)符號(hào)整數(shù)。指針想要訪問(wèn)這個(gè)數(shù)據(jù),但是他沒(méi)有換通行證,因?yàn)樗X(jué)得這樣太麻煩了。他覺(jué)得反正我的通行證int是4字節(jié)的,用于訪問(wèn)同樣是4字節(jié)的unsigned int應(yīng)該沒(méi)問(wèn)題吧?他偷偷地拿出水筆,在通行證上的“int”字樣前潦草地寫(xiě)上“unsigned”前綴,改成了“unsigned int”,訪問(wèn)了這個(gè)unsigned int數(shù)據(jù),沒(méi)有被發(fā)現(xiàn)。 他后來(lái)翻看員工手冊(cè),才知道,原來(lái)每次派發(fā)整數(shù)類型的通行證時(shí),都會(huì)附贈(zèng)一張貼紙,貼紙上寫(xiě)著unsigned,把unsigned貼紙貼在通行證上作為前綴,即可訪問(wèn)整數(shù)的無(wú)符號(hào)版本的數(shù)據(jù)。而他剛才隨手把那貼紙扔了,自己寫(xiě)上unsigned也達(dá)到了同樣的效果。之所以開(kāi)了這個(gè)后門或許是為了無(wú)符號(hào)和有符號(hào)類型轉(zhuǎn)換的方便起見(jiàn),他心想。 他來(lái)到精加工車間,發(fā)現(xiàn)里面都是一些結(jié)構(gòu)體類型,他看到一個(gè)結(jié)構(gòu)體struct C { int i; short s; };他想要頂撞一下規(guī)則,于是拿著int通行證去訪問(wèn)這個(gè)結(jié)構(gòu)體的數(shù)據(jù),他本以為會(huì)像剛才那樣遭到阻攔,卻不料成功訪問(wèn)了,沒(méi)有被工作人員阻止。原來(lái)根據(jù)規(guī)則,只要結(jié)構(gòu)體或聯(lián)合體的成員中含有一個(gè)通行證上的類型,也是可以訪問(wèn)的。這應(yīng)該是出于有時(shí)候需要取成員變量指針的方便,他心想。 指針更加高興了,他覺(jué)得自己很厲害。他繼續(xù)尋找其他類型的數(shù)據(jù),試圖用他的通行證訪問(wèn)它們。他找到了一個(gè)叫做float的數(shù)據(jù),它是一個(gè)4字節(jié)的單精度浮點(diǎn)數(shù)。指針想要訪問(wèn)這個(gè)數(shù)據(jù),但是他沒(méi)有換通行證,因?yàn)樗X(jué)得這樣太麻煩了。他覺(jué)得反正我的通行證int是4字節(jié)的,用于訪問(wèn)同樣是4字節(jié)的float應(yīng)該沒(méi)問(wèn)題吧?他從背包里掏出一款“reinterpret_cast牌”的修正帶,偷偷地把他的通行證上的“unsigned int”字樣涂掉,改成了“float”。他拿著篡改的通行證訪問(wèn)了這個(gè)float數(shù)據(jù),他違規(guī)的行為沒(méi)有被發(fā)現(xiàn)。 指針?lè)浅8吲d了,他覺(jué)得自己無(wú)所不能。但是就在這時(shí)候,一聲巨響打破了工廠的平靜。原來(lái),指針在訪問(wèn)float數(shù)據(jù)時(shí),不小心改變了它的值。這個(gè)float數(shù)據(jù)正好是工廠里最重要的數(shù)據(jù)之一,它控制著工廠的運(yùn)轉(zhuǎn)。由于指針改變了它的值,而工廠管委會(huì)覺(jué)得自己只發(fā)過(guò)int的通行證,認(rèn)為不會(huì)有人修改過(guò)這個(gè)float值,因?yàn)閒loat值只有頒發(fā)了float通行證才能修改。并不知情的工程師想當(dāng)然地拿著錯(cuò)誤的float數(shù)據(jù)進(jìn)行運(yùn)算,導(dǎo)致工廠出現(xiàn)了嚴(yán)重的故障,并且開(kāi)始爆炸。 指針嚇壞了,他想要逃跑,但是已經(jīng)來(lái)不及了。工廠里所有的安保人員都發(fā)現(xiàn)了指針,并且追捕他。指針被抓住了,并且被嚴(yán)厲地批評(píng)教育的一番。
經(jīng)歷了這一次的教訓(xùn),他認(rèn)識(shí)到了隨意用reinterpret_cast涂改通行證的危險(xiǎn)。一位名叫“本賈尼·斯特勞斯特盧普”的安全人員告訴他,如果想要安全低在float和int之間按位轉(zhuǎn)換,可以用以下這些方法: 1. 聯(lián)系一名叫做std::launder的清潔人員,他會(huì)幫你重新打印通行證,由于他會(huì)利用對(duì)講機(jī)通知管理人員進(jìn)行調(diào)度,所以他幫你涂改通行證是安全的。 2. 當(dāng)你所在的工廠老板是“GNUC”時(shí),可以走進(jìn)辦公室對(duì)他說(shuō)一句-fno-strict-aliasing,開(kāi)明的他就會(huì)幫你廢除這個(gè)規(guī)則,strict-aliasing規(guī)則廢除后,通行證就可以隨意涂改,但由于每對(duì)通行證之間都可能指向同一個(gè)對(duì)象,管理人員不得不假定每一個(gè)指針都是aliasing的情況,所以也會(huì)妨礙到管理人員對(duì)工程的排班和優(yōu)化,對(duì)工廠流程效率產(chǎn)生負(fù)面影響。 3. 還可以用一臺(tái)叫做std::bit_cast的“重塑機(jī)”進(jìn)行轉(zhuǎn)換,不過(guò)他和,std::bit_cast需要把float對(duì)象塞到機(jī)器里,并吐出int對(duì)象,而不是插入指向float的通行證,他內(nèi)部是通過(guò)把源類型用memcpy逐字節(jié)拷貝到unsigned char暫存,然后重新reinterpret_cast成目標(biāo)類型讀取的,由于。 4. 可以用一個(gè)聯(lián)合體union { int i; float f; }進(jìn)行轉(zhuǎn)換,抽象來(lái)說(shuō)是由于規(guī)則說(shuō)通行證可以訪問(wèn)聯(lián)合體的成員,因此可以借助聯(lián)合體作為橋梁來(lái)進(jìn)行轉(zhuǎn)換,具體來(lái)說(shuō)是因?yàn)槁?lián)合體創(chuàng)建時(shí),管理人員就知道這兩個(gè)類型之間可能產(chǎn)生按位轉(zhuǎn)換,從而對(duì)結(jié)構(gòu)體內(nèi)的所有成員留個(gè)心眼,不會(huì)產(chǎn)生調(diào)度事故。 經(jīng)過(guò)一頓安全教育后,指針害怕極了,他再也不敢亂動(dòng)不兼容類型的數(shù)據(jù)。有一次指針需要修過(guò)一個(gè)float值,有了上次的教訓(xùn),他不敢直接拿著int通行證修改float的值。于是他找來(lái)一位名叫std::launder的專業(yè)清潔工,只見(jiàn)清潔工拿起int通行證,在上面進(jìn)行了一波“洗刷刷洗刷刷”,于是就把通行證上int的水印去掉了。然后清潔工打了個(gè)電話給管委會(huì),申請(qǐng)報(bào)備了float的訪問(wèn)權(quán)限,經(jīng)過(guò)管委會(huì)簽字同意后,才往“洗刷刷”刷干凈的通行證印上“float”的字樣。管委會(huì)說(shuō),給,這是你船新的float通行證,用這張官方報(bào)備過(guò)的通行證,就可以安全訪問(wèn)float值了。指針用float通行證修改了float,這時(shí)另一個(gè)指針也想修改同一個(gè)float值,他也申請(qǐng)了一份float通行證。管委會(huì)發(fā)現(xiàn)當(dāng)前批出去了兩份float通行證,于是派出安全專家維護(hù)float車間的秩序,讓指針和另一個(gè)指針有序訪問(wèn)float值,工廠沒(méi)有發(fā)生故障,今天又是和平的一天。