頂層const和底層const
const和其他關鍵字
最近在複習c++ primer,把以前沒注意到的都深入研究了一下。
此篇部落格的結論都建立於c++11或者c++14的新標準上,編譯器為vs2015 community版本,g++可能會有較大出入(這點筆者已經在其他部落格上驗證)
c語言中的巨集機制被繼承到了c++,巨集是一種替換行為,而且是完全的字元替換,發生在預編譯期,所以在這種機制下,編譯期無法對巨集進行任何的型別檢查,我們來看彙編中,巨集是如何實現的:
#define define_var 100
int temp_num = define_var;
008c6478 mov dword ptr [temp_num],64h
可以看到,巨集本身是不分配任何記憶體的,而是在預編譯時進行字元替換。
值得慶幸的是,現代的大多數ide,都能在寫**階段就判斷程式設計師的大多數錯誤,當然也包括了巨集內部的型別檢查。const被使用至今,已經從當初巨集的替代品,發展成了乙個很複雜的東西,特別是從c++11和14引入弱型別的特性(auto和delctype等關鍵字)後,const的使用稍不注意就會發生很多錯誤。
const是編譯期的行為
const是一種編譯期的行為,在編譯期內,具有型別,會執行型別檢測,所以他能由編譯器指出程式在執行之前的錯誤,關於這點,我會在後面給出驗證。
const宣告占用記憶體
最初const和巨集最大的區別可能就在於此,以前有種論調認為const宣告的變數位於程式的符號表中,經筆者證明,並非是這樣,或者說並非僅僅是這樣。
至於const占用記憶體,我們可以從下面一段彙編中看出來:
const
int ci = 0;
01081fa8 mov dword ptr [ci],0
const
int &i = 0;
01081fc2 mov dword ptr [ebp-48h],0
01081fc9 lea eax,[ebp-48h]
01081fcc mov dword ptr [i],eax
我們知道,直接用常量表示式初始化引用是不合法的,但是常量引用是可以是被任意表示式初始化的,我們可以看到程式提前宣告了一塊空間用於存放「0」,然後再將引用繫結到指定位址。
const是偽常量
const的設計非常奇怪,雖然在編譯期進行嚴格的型別檢查,但是卻不在執行期給予任何保障,而且最重要的是,允許常指標型別轉化為普通指標型別,引用型別與之相同,請看下面的語句:
const
int cvar = -100;
const
int* p = &cvar;
int* x = (int*)&cvar;
*x = 5;
cout
<< "cvar: "
<< cvar << endl;
cout
<< "p: "
<< *p << endl;
cout
<< "x: "
<< *x << endl;
cout
<< "origin rom: "
<< &cvar << endl;
cout
<< "p rom: "
<< p << endl;
cout
<< "x rom: "
<< x << endl;
輸出結果是:
cvar: -100筆者剛看到這一塊的時候也覺得很奇怪,有兩個奇怪的點:p: 5
x: 5
origin rom: 00aff988
p rom: 00aff988
x rom: 00aff988
1、雖然記憶體相同,但是輸出的值卻不同,分析彙編之後才得出結論,由於輸入輸出流的彙編有點麻煩,我們看這一句:
int test = cvar;
00912610 mov dword ptr [test],0ffffff9ch
我們可以看到,雖然cvar在初始化的時候分配了記憶體,但是當編譯器在遇到cvar這個字串的時候,還是採取了和巨集相同的方案——進行展開替換。
2、常量的值被指標修改了,其實這也理所應當,const的機制沒有對記憶體有任何的操作,一旦進入執行期,存放const變數的記憶體卻沒有任何標記證明它是不可修改的,自然指標會把它當做普通記憶體來處理,這一點筆者不是很明白語言設計者的思路,為什麼要允許這種強制轉換?所以c++的靈活性也常常為人所詬病——太過靈活導致太容易出bug。
notice:有的記憶體塊是只能寫的(比如main函式之外的const申請的就是這樣的記憶體),這時如果用指標修改常量會發生錯誤
const和復合型別
const和指標、引用筆者不想多說,因為這些全是一些很生硬的規則,如果讀者還不清楚常量指標和 指向常量的指標等知識,可以去翻閱c++primer p54-p57
概念頂層const和底層const是為了方便常量的賦值,型別推斷等操作而提出的概念。
一般來講,所有非復合型別的常物件,是乙個頂層const,表示該物件本身是乙個常量。
而復合型別,如果繫結的物件是常量,我們稱其為底層const,如果我們說這種繫結關係是恆定不變的,那麼叫做頂層const。
const int *p指向常量的指標,這是底層const拷貝操作int *const p常量指標,這是頂層const
const int *const p,第乙個const為底層const,第二個是頂層const
由於引用本身就是固定的繫結關係,所以常引用都是底層const
在拷貝(賦值)操作時,頂層const被忽略,但是拷入物件和拷出物件必須擁有相同的底層const資格,具體如下:
int originvar = 0;
0091249f mov dword ptr [originvar],0
int* po0 = &originvar;
009124a6 lea eax,[originvar]
009124a9 mov dword ptr [po0],eax
const本身就已經有很多複雜的行為,當它和其他關鍵字混合使用時,將產生更多的誤區。
constexpr
關於constexpr筆者使用得很少,這裡提出一點:const int *p指向常量的指標,而constexpr int *p卻是指向int的常量指標。
auto
c++11很重要的型別推斷特性,當auto用於推斷const物件時,會忽略頂層const,保留底層const,這一點和拷貝操作時一樣,但是要注意指標和引用的賦值結構。
Overlapped I O模型深入分析
原文 http www.yuanma.org data 2007 0227 article 2351.htm 簡述 typedef struct o dword internal dword internalhigh dword offset 指定檔案的位置,從該位置傳送資料,檔案位置是相對檔案開始...
C 中const的實現機制深入分析
c語言以及c 語言中的const究竟表示什麼?其具體的實現機制又是如何實現的呢?本文將對這兩個問題進行一些分析,需要了解的朋友可以參考下 問題 c語言以及c 語言中的const究竟表示什麼?其具體的實現機制又是如何實現的呢?本文將對這兩個問題進行一些分析,簡單解釋const的含義以及實現機制。問題分...
C 中const的實現機制深入分析
問題 c語言以及c 語言中的const究竟表示什麼?其具體的實現機制又是如何實現的呢?本文將對這兩個問題進行一些分析,簡單解釋const的含義以及實現機制。問題分析 簡單的說const在c語言中表示唯讀的變數,而在c 語言中表示常量。關於const在c與c 語言中的使用以及更多的區別,以後有時間另開...