這小段文章要理清楚的是,在C語言中,const是如何保證變量不被修改的?shell
咱們能夠想到兩種方式:編程
第一種,由編譯器來阻止修改const變量的語句,讓這種程序不能經過編譯;函數
第二種,由操做系統來阻止,即把const 的內存地址訪問權限標記爲「只讀」,一旦運行中的程序試圖修改它,就會產生異常,終止進程。優化
上面想到的這兩種方式,都能達到讓某一變量的值不被修改的目的,那麼到底是哪種呢?咱們寫兩個例子來看一看。操作系統
先來看一個簡單的例子,源文件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; }
編譯,會收到一個 warning:code
$ 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;
忽略之,運行程序:對象
$ ./const1 initial: 10 Segmentation fault (core dumped)
運行出錯了,報錯是「segmentation fault」,即「段錯誤」,它是在提醒咱們,程序中用錯誤的權限訪問了內存某區域。這說明,操做系統把變量$a$加載到了一段只讀內存區域之中,所以對該區域地址的寫操做將引起異常,這是由操做系統的內存保護機制決定的。進程
也就是說,在這段程序裏,const的只讀屬性是由操做系統來實現的,而不是由編譯器來實現的(編譯器只拋出了warning,並無阻止編譯經過)。內存
這對嗎?不徹底對,咱們來看另外一個例子,源文件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;
忽略之,運行程序:
./const2 initial: 10 modified: 1
咦?怎麼成功運行了,並且a的值還被順利修改了?
結合以上兩個例子,咱們能夠得出如下推測:
const只是C語言中的一種對變量的修飾符,例子中的a,與其說是「常量」,不如說是「不打算修改的變量」。它只是語法上的一種聲明,它的做用就是告訴編譯器「我不想修改它」,所以編譯器會從語法上檢查程序中是否有修改它的語句(例如「a=1;」),一旦發現這種「違背初衷」的語句,就會報錯阻止你。
然而,編譯器所阻止的僅僅是對a這個符號對應值的修改而已,卻並不阻止對這個地址的值的修改,源文件「const2.c」之因此能順利經過編譯且正常運行,就是由於它利用一個名字不叫a的指針指向它,從而繞過了編譯器的語法檢查。
打個比方,周樹人的筆名叫魯迅,警察只知道要抓魯迅,這時候他就能夠用一句「大家抓魯迅跟我周樹人有什麼關係?」來騙過他們。
從這個角度來講,const的做用是靠編譯器僅僅從語法檢查來實現的,所以存在運行時的漏洞。
那麼爲何「const1.c」就不能正常運行呢?
仔細看這兩個源程序,區別僅僅在於,在「const1.c」中,a被聲明爲全局變量,而在「const2.c」中,它被聲明爲main函數中的一個局部變量。全局變量與局部變量的區別在於,前者會在程序開始運行以前就被加載,加載後會一直留在內存中,且加載的位置在數據區,直到程序退出;後者只有在運行到它時纔會被加載,且加載的位置是運行時的棧幀,一旦超出做用於就會被回收。
所以,編譯器會對被聲明爲全局變量的const int a進行優化,把它放到只讀內存區內,這一內存區的權限是「read\ only」,權限信息由操做系統所維護的段表來保存,程序每訪問某地址時,操做系統都會檢測其訪問權限是否合法。「const2.c」中企圖用「寫」的方式來訪問「只讀」的段,天然會報出「segment fault"的錯了。
從這個角度來講,當a是全局變量時,編譯器把本來只是「不打算修改的變量」優化成了「真正的常量」,而後交給操做系統去維持其不變屬性。
綜上所述,C的初衷只是讓編譯器去保證$const$的不變屬性,這一屬性有漏洞(能夠用指針去騙過編譯器修改它),因此當const修飾的對象是全局變量時(全局變量很重要,由於不少源文件都要訪問它,牽一髮而動全身,因此不該輕易更改),編譯器知道本身的能力有限,只能管得了編譯,管不了運行時如何,因此優化了語句把它編程真正的常量,讓操做系統的內存保護功能來履行這一職責。
這一優化,並非C規定的,而是編譯器廠商出於實際應用的考慮做出的選擇。
以上,是我根據編譯器和程序運行時的行爲所作的推測,這一思路並不穩當,只是我在編程時遇到了上述兩個例子的困惑,又沒找到說得很清楚的資料,因此就寫出來了,若要進一步驗證,應該查看編譯後的可執行文件分段狀況,我偷了個懶沒看,暫時放在這裏。
若是推測不正確,但願有前輩指出。
歡迎交流。