C語(yǔ)言中的const如何保證變量不被修改
這小段文章要理清楚的是,在C語(yǔ)言中,const是如何保證變量不被修改的?
我們可以想到兩種方式:
第一種,由編譯器來(lái)阻止修改const變量的語(yǔ)句,讓這種程序不能通過(guò)編譯;
第二種,由操作系統(tǒng)來(lái)阻止,即把const 的內(nèi)存地址訪(fǎng)問(wèn)權(quán)限標(biāo)記為“只讀”,一旦運(yùn)行中的程序試圖修改它,就會(huì)產(chǎn)生異常,終止進(jìn)程。
上面想到的這兩種方式,都能達(dá)到讓某一變量的值不被修改的目的,那么究竟是哪一種呢?我們寫(xiě)兩個(gè)例子來(lái)看一看。
先來(lái)看一個(gè)簡(jiǎn)單的例子,源文件const.c:
#include <stdio.h> const int a=10; int main() { int *p=&a; printf("initial: %d\n",a); *p=1; printf("modified: %d\n",a); return 0; }
編譯,會(huì)收到一個(gè) warning:
$ gcc -o const1 const1.c
const.c: In function ‘main':
const.c:7:12: warning: initialization discards ‘const' qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p=&a;
忽略之,運(yùn)行程序:
$ ./const1
initial: 10
Segmentation fault (core dumped)
運(yùn)行出錯(cuò)了,報(bào)錯(cuò)是“segmentation fault”,即“段錯(cuò)誤”,它是在提醒我們,程序中用錯(cuò)誤的權(quán)限訪(fǎng)問(wèn)了內(nèi)存某區(qū)域。這說(shuō)明,操作系統(tǒng)把變量$a$加載到了一段只讀內(nèi)存區(qū)域之中,因此對(duì)該區(qū)域地址的寫(xiě)操作將引發(fā)異常,這是由操作系統(tǒng)的內(nèi)存保護(hù)機(jī)制決定的。
也就是說(shuō),在這段程序里,const的只讀屬性是由操作系統(tǒng)來(lái)實(shí)現(xiàn)的,而不是由編譯器來(lái)實(shí)現(xiàn)的(編譯器只拋出了warning,并沒(méi)有阻止編譯通過(guò))。
這對(duì)嗎?不完全對(duì),我們來(lái)看另一個(gè)例子,源文件const2.c:
#include <stdio.h> int main() { const int a=10; int *p=&a; printf("initial: %d\n",a); *p=1; printf("modified: %d\n",a); return 0; }
編譯,還是收到同樣的warning:
$ gcc -o const2 const2.c
const.c: In function ‘main':
const.c:6:12: warning: initialization discards ‘const' qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p=&a;
忽略之,運(yùn)行程序:
./const2
initial: 10
modified: 1
咦?怎么成功運(yùn)行了,而且a的值還被順利修改了?
結(jié)合以上兩個(gè)例子,我們可以得出以下推測(cè):
const只是C語(yǔ)言中的一種對(duì)變量的修飾符,例子中的a,與其說(shuō)是“常量”,不如說(shuō)是“不打算修改的變量”。它只是語(yǔ)法上的一種聲明,它的作用就是告訴編譯器“我不想修改它”,因此編譯器會(huì)從語(yǔ)法上檢查程序中是否有修改它的語(yǔ)句(例如“a=1;”),一旦發(fā)現(xiàn)這種“違背初衷”的語(yǔ)句,就會(huì)報(bào)錯(cuò)阻止你。
然而,編譯器所阻止的僅僅是對(duì)a這個(gè)符號(hào)對(duì)應(yīng)值的修改而已,卻并不阻止對(duì)這個(gè)地址的值的修改,源文件“const2.c”之所以能順利通過(guò)編譯且正常運(yùn)行,就是因?yàn)樗靡粋€(gè)名字不叫a的指針指向它,從而繞過(guò)了編譯器的語(yǔ)法檢查。
打個(gè)比方,周樹(shù)人的筆名叫魯迅,警察只知道要抓魯迅,這時(shí)候他就可以用一句“你們抓魯迅跟我周樹(shù)人有什么關(guān)系?”來(lái)騙過(guò)他們。
從這個(gè)角度來(lái)說(shuō),const的作用是靠編譯器僅僅從語(yǔ)法檢查來(lái)實(shí)現(xiàn)的,因此存在運(yùn)行時(shí)的漏洞。
那么為什么“const1.c”就不能正常運(yùn)行呢?
仔細(xì)看這兩個(gè)源程序,區(qū)別僅僅在于,在“const1.c”中,a被聲明為全局變量,而在“const2.c”中,它被聲明為main函數(shù)中的一個(gè)局部變量。全局變量與局部變量的區(qū)別在于,前者會(huì)在程序開(kāi)始運(yùn)行之前就被加載,加載后會(huì)一直留在內(nèi)存中,且加載的位置在數(shù)據(jù)區(qū),直到程序退出;后者只有在運(yùn)行到它時(shí)才會(huì)被加載,且加載的位置是運(yùn)行時(shí)的棧幀,一旦超出作用于就會(huì)被回收。
因此,編譯器會(huì)對(duì)被聲明為全局變量的const int a進(jìn)行優(yōu)化,把它放到只讀內(nèi)存區(qū)內(nèi),這一內(nèi)存區(qū)的權(quán)限是“read\ only”,權(quán)限信息由操作系統(tǒng)所維護(hù)的段表來(lái)保存,程序每訪(fǎng)問(wèn)某地址時(shí),操作系統(tǒng)都會(huì)檢測(cè)其訪(fǎng)問(wèn)權(quán)限是否合法?!癱onst2.c”中企圖用“寫(xiě)”的方式來(lái)訪(fǎng)問(wèn)“只讀”的段,自然會(huì)報(bào)出“segment fault"的錯(cuò)了。
從這個(gè)角度來(lái)說(shuō),當(dāng)a是全局變量時(shí),編譯器把原本只是“不打算修改的變量”優(yōu)化成了“真正的常量”,然后交給操作系統(tǒng)去維持其不變屬性。
綜上所述,C的初衷只是讓編譯器去保證$const$的不變屬性,這一屬性有漏洞(可以用指針去騙過(guò)編譯器修改它),所以當(dāng)const修飾的對(duì)象是全局變量時(shí)(全局變量很重要,因?yàn)楹芏嘣次募家L(fǎng)問(wèn)它,牽一發(fā)而動(dòng)全身,所以不應(yīng)輕易更改),編譯器知道自己的能力有限,只能管得了編譯,管不了運(yùn)行時(shí)如何,所以?xún)?yōu)化了語(yǔ)句把它編程真正的常量,讓操作系統(tǒng)的內(nèi)存保護(hù)功能來(lái)履行這一職責(zé)。
這一優(yōu)化,并不是C規(guī)定的,而是編譯器廠(chǎng)商出于實(shí)際應(yīng)用的考慮作出的選擇。
以上,是我根據(jù)編譯器和程序運(yùn)行時(shí)的行為所做的推測(cè),這一思路并不妥當(dāng),只是我在編程時(shí)遇到了上述兩個(gè)例子的困惑,又沒(méi)找到說(shuō)得很清楚的資料,所以就寫(xiě)出來(lái)了,若要進(jìn)一步驗(yàn)證,應(yīng)該查看編譯后的可執(zhí)行文件分段情況,我偷了個(gè)懶沒(méi)看,暫時(shí)放在這里。
如果推測(cè)不正確,希望有前輩指出。
總結(jié)
到此這篇關(guān)于C語(yǔ)言中const如何保證變量不被修改的文章就介紹到這了,更多相關(guān)C語(yǔ)言const變量不修改內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
總結(jié)了24個(gè)C++的大坑,你能躲過(guò)幾個(gè)
這篇文章主要介紹了總結(jié)了24個(gè)C++的大坑,你能躲過(guò)幾個(gè),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-05-05C++ OpenCV實(shí)現(xiàn)與添加椒鹽噪聲和高斯噪音
圖像噪聲是圖像在獲取或是傳輸過(guò)程中受到隨機(jī)信號(hào)干擾,妨礙人們對(duì)圖像理解及分析處理的信號(hào),本文為大家整理了C++結(jié)合OpenCV為圖像添加椒鹽噪聲和高斯噪音的代碼,需要的可以收藏一下2023-09-09C++設(shè)計(jì)模式之組合模式(Composite)
這篇文章主要為大家詳細(xì)介紹了C++設(shè)計(jì)模式之組合模式Composite,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04C++深入學(xué)習(xí)之徹底理清重載函數(shù)匹配
C++ 不允許變量重名,但是允許多個(gè)函數(shù)取相同的名字,只要參數(shù)表不同即可,這叫作函數(shù)的重載,下面這篇文章主要給大家介紹了關(guān)于C++深入學(xué)習(xí)之徹底理清重載函數(shù)匹配的相關(guān)資料,需要的朋友可以參考下2019-01-01c++創(chuàng)建二維動(dòng)態(tài)數(shù)組與內(nèi)存釋放問(wèn)題
這篇文章主要介紹了c++創(chuàng)建二維動(dòng)態(tài)數(shù)組與內(nèi)存釋放問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06C++中對(duì)象的賦值與復(fù)制操作詳細(xì)解析
對(duì)象之間的賦值也是通過(guò)賦值運(yùn)算符“=”進(jìn)行的。本來(lái)賦值運(yùn)算符“=”只能用來(lái)對(duì)單個(gè)的變量賦值,現(xiàn)在被擴(kuò)展為兩個(gè)同類(lèi)對(duì)象之間的賦值,這是通過(guò)對(duì)賦值運(yùn)算符的重載實(shí)現(xiàn)的2013-10-10C++基于棧的深搜算法實(shí)現(xiàn)馬踏棋盤(pán)
這篇文章主要為大家詳細(xì)介紹了C++基于棧的深搜算法實(shí)現(xiàn)馬踏棋盤(pán),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02