一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

CPU眼里的:常量(const)

 山峰云繞 2023-05-27 發(fā)布于貴州

https://m.toutiao.com/is/UftrH7N/?= 


常量為什么不能被改寫?有魔法嗎?是的,有魔法

提出問題

什么是常量?常量就是:數(shù)值不能變化的變量。如果如此簡單、易懂的定義,你都能挑出毛病,那可能就真的是在:嘩眾取寵了。提出問題的人,可能需要在自己身上找找原因了。

但你是否考慮過一個問題:是什么機(jī)制,在保證常量,不能改變這個初衷呢?要知道,市面上所有的內(nèi)存條,都是可讀、可寫、可改的,想試圖阻止 CPU 的寫操作,談何容易呀!

是程序員的自律?還是編譯器的銅墻鐵壁?就讓我們的用 CPU 的視角,解讀一個常量背后的故事。

代碼分析

打開 Compiler Explorer,定義一個常量a,再定義一個變量b;為了防止編譯器優(yōu)化常量 a 的讀寫操作,我們特意在定義常量 a 的時候加上了關(guān)鍵字:volatile;至于 volatile 的工作原理,請查看“CPU眼里的volatile”。

接著,寫一個函數(shù)func1,用來讀取并返回常量a的值;然后,再寫一個函數(shù)func2,用來讀取并返回變量b的值:

老規(guī)矩,不要關(guān)心匯編指令的具體含義,我們只比較二者的差異,很顯然除了a、b的內(nèi)存地址不同外,兩個函數(shù)的匯編指令完全相同!編譯器并沒有對變量和常量作任何的區(qū)分和特殊處理。

難道,我們又要得到一個聳人聽聞的結(jié)論:變量與常量,本質(zhì)上沒有任何區(qū)別?且慢,我們再看看寫操作,先給變量b賦值,編譯通過,沒有問題。

再給常量a,賦相同的值:1

inf func1(){ a = 1; return 0;}

雖然我們并沒有試圖改變常量a的值,但我們的代碼,還是會被編譯器無情的拒絕!

看來,常量的含義不僅僅是:它的值不可改變,原來它是徹底的拒絕寫操作呀。即便是你并不打算改變它的值??磥?const 關(guān)鍵字還真能保護(hù)常量的值,不會被重新寫入。

為了解除編譯器層面的禁止,我們需要為常量a稍微換個馬甲,幫助它繞過編譯器的檢查:

如你所見,對常量a作一個向普通 int 類型的轉(zhuǎn)換,這樣就可以通過編譯了。

再比較一下函數(shù)func1和函數(shù)func2的匯編指令,如你所見:除了a、b的內(nèi)存地址不同外,它們的匯編指令是完全一致的!

好了,如此看來:常量a和變量b,在讀、寫操作上面,都是完全一致的。排除編譯器在語法層面,對常量的保護(hù),我們能否認(rèn)為:常量與變量的本質(zhì)是完全相同的呢?

到底是否相同,我們實際運(yùn)行一下就知道了。寫一個函數(shù)main,先調(diào)用一下函數(shù)func2,一切正常;然后我們再調(diào)用一下函數(shù)func1:

如你所見,在調(diào)用函數(shù)func1的時候程序出錯,返回值:139意味著:段錯誤(segmentation fault)這是為什么呢?讓我們分別打印:常量a和變量b的內(nèi)存地址:

如你所見,雖然a、b是依次定義的,但是內(nèi)存地址的距離,卻超過了:8K 字節(jié)。它們顯然不在同一個內(nèi)存頁里面,如“CPU眼里的程序運(yùn)行”所說,程序運(yùn)行前,代碼中的全局變量、常量會被拷貝到數(shù)據(jù)段。只是這個數(shù)據(jù)段還會被細(xì)分成:只讀數(shù)據(jù)段和可讀、寫數(shù)據(jù)段,因此,它們所在的內(nèi)存頁的讀、寫屬性可能是不同的。

我們猜:變量b所在的內(nèi)存頁,在MMU映射表中的屬性是:可讀、可寫的;常量a所在的內(nèi)存頁,在MMU映射表中屬性是:只讀的。因此,當(dāng)我們強(qiáng)行對a進(jìn)行:寫操作時,就會觸發(fā)CPU異常,導(dǎo)致程序崩潰!

因為,內(nèi)存頁的讀寫屬性,不僅對常量a所在的內(nèi)存有效,甚至對整個4KB的內(nèi)存頁,都是有效的。所以,即使試圖對a周圍的內(nèi)存,進(jìn)行寫操作也是不被允許的:

夸張的說:現(xiàn)代操作系統(tǒng)和編程語言的實現(xiàn),都離不開MMU這個好幫手。更多的MMU知識,還可以查看“CPU眼里的虛擬內(nèi)存”。

總結(jié)

  1. 常量并不僅僅是:不能改變初值的變量,也是:不允許對其二次寫入的變量。除此之外,它跟普通變量一樣,也是某個內(nèi)存地址的別名。
  2. 編譯器可以通過對代碼的解讀,阻止明顯的、針對常量的寫操作。但由于常量,跟變量一樣,也只是內(nèi)存地址的別名。所以程序員很容易通過指針、或類型轉(zhuǎn)換的方式,逃過編譯器的檢查。
  3. 真正保證常量不被寫入的安全閥是:MMU,它能從物理上阻止對特定內(nèi)存的讀寫。如果常量所在的內(nèi)存頁是不可讀寫的,例如:read only數(shù)據(jù)段,那么寫操作會被MMU阻止,并產(chǎn)生CPU異常。

但如果常量所在的內(nèi)存頁是可讀、寫的,例如:函數(shù)內(nèi)部定義的臨時的“?!背A浚捎凇岸褩!北旧硎强勺x、可寫的,所以在逃過編譯器檢查后,“?!背A恳彩强梢皂樌麑懭氲摹?/p>

熱點問題

Q1:如果我通過cast強(qiáng)行轉(zhuǎn)換成非const變量呢?

A1:效果是一樣的,你或許可以繞過編譯器的檢查,但在真正作寫操作的時候,會被MMU察覺到。

Q2:單片機(jī),例如:STM32,沒有MMU,它能阻止對常量的寫操作嗎?

A2:雖然單片機(jī)可能沒有MMU,但它仍然有可能阻止程序?qū)ΤA康膶懖僮?。因為,單片機(jī)在編譯完程序后,往往會通過專門的設(shè)備把程序中的:常量、函數(shù),燒寫在:ROM上面。

由于ROM的寫入過程比較特殊,需要配合特定的設(shè)備和總線操作;CPU不能通過常規(guī)的內(nèi)存讀、寫指令(例如:MOV指令)來改寫ROM上的信息,所以,代碼對常量的寫操作,即使可以順利運(yùn)行,但也很難真正改寫ROM上的常量。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    日韩一区二区三区久久| 99精品人妻少妇一区二区人人妻| 黄片在线免费观看全集| 国内真实露脸偷拍视频| 国产又粗又长又大的视频| 欧美精品二区中文乱码字幕高清| 91久久精品国产一区蜜臀| 国产一级内射麻豆91| 91久久精品中文内射| 欧美日韩视频中文字幕| 女生更色还是男生更色| 午夜国产精品福利在线观看| 亚洲国产成人久久一区二区三区| 国产精品白丝久久av| 国产亚洲欧美一区二区| 欧美日韩国产综合在线| 久久成人国产欧美精品一区二区| 色综合久久六月婷婷中文字幕| 久久精品一区二区少妇| 中文字幕精品一区二区年下载| 性欧美唯美尤物另类视频| 欧美一区日韩一区日韩一区| 区一区二区三中文字幕| 国产成人人人97超碰熟女| 日本少妇aa特黄大片| 婷婷开心五月亚洲综合| 一二区中文字幕在线观看| 99免费人成看国产片| 国产精品欧美在线观看| 成人精品一级特黄大片| 久久综合九色综合欧美| 玩弄人妻少妇一区二区桃花| 老司机亚洲精品一区二区| 国产二级一级内射视频播放| 日韩特级黄片免费观看| 国产高清三级视频在线观看| 国产三级黄片在线免费看| 在线观看视频日韩精品| 欧美精品在线观看国产| 国产一区二区三区午夜精品| 日韩av亚洲一区二区三区|