深入分析const關鍵字模型

2021-07-22 05:32:44 字數 3562 閱讀 4639

頂層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 語言中的使用以及更多的區別,以後有時間另開...