日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++的常量折叠(一)

發布時間:2023/11/29 c/c++ 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++的常量折叠(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言


前幾天女票問了我一個阿里的面試題,是有關C++語言的const常量的,其實她一提出來我就知道考察的點了:肯定是const常量的內存不是分配在read-only的存儲區的,const常量的內存分配區是很普通的棧或者全局區域。也就是說const常量只是編譯器在編譯的時候做檢查,根本不存在什么read-only的區域。

所以說C++的const常量和常量字符串是不同的,常量字符串是存儲在read-only的區域的,他們的具體的存儲區域是不同的。

?

就好像楊立翔老師在上課舉得那個例子(講的東西忘得差不多了,但是例子都記得,什么撕戶口本,月餅模子之類的):

場景:老式的電影放映機,門口的檢票員,最后排那個座位上的放映師傅,好多的觀眾席。

你的電影票上有你的名字,檢票員的手里有個名字和座位號的對應表,檢票員不允許觀眾亂坐位置。

所以游戲就這樣開始了,電影院的每一個觀眾席都是const常量類型的,檢票員就是編譯器,在觀眾進門的時候,每個人都必須坐自己票上的位子,也就是說觀眾必須拿著自己的票進門。你拿著別人的票就不讓你進去,所以就不能更換座位。

類比到C++語言上,場景應該是這樣的:對于哪些const常量,在編譯的時候就已經決定了,他們在內存的存儲位置,你如果直接改這個常量的值是不合法的。就好像你不能拿著一張別人的電影票進電影院,因為檢票員手里有一張座位號到姓名的映射表,他知道哪個座位該坐誰,并且不讓觀眾隨意調換(這個例子尼瑪真的好形象啊,楊老師說的沒有這么好,我后期根據自己的理解稍微添油加醋)。其實檢票員手里的那張映射表就是編譯器的符號表,是不是真的很形象,關于這個符號表后面說。

那么新的問題來了,觀眾進到電影院以后,我跟旁邊的哥們說一聲,我給你100塊,你給我換一下位置吧,如果他同意了,我們就能換。檢票員管不著,進都進來了,還能怎樣。所以兩個人就調換了位子。

C++的const常量一樣是這個樣子,編譯器在編譯的時候要查看它手里的符號表,如果你拿的是一個const常量,并且你要修改這個常量的值,他就拒絕。但是這種檢查值會發生在編譯器,如果我能繞過編譯器,在運行的時候修改這個const的值,將會是很easy的一件事,就像前面說的,我給那哥們100塊就搞定了。所以說const常量的存儲空間同其他的變量的存儲空間沒有任何的區別,它的這種常量不允許就該的檢查只發生在編譯器的編譯階段。

但是常量字符串的存儲位置就不同了,它的存儲位置是read-only的區域,就像電影院最后的那個放映師的位置,那里是一個特殊的位置,是真的不允許隨便的調換,你給他1000也不給你換,因為師傅要在那個位置放映,不然沒法看電影。當然了,實現這種read-only的存儲區域也很簡單,把那個內存的頁(page)的屬性標記為只讀的就好了。

對了,當時我還想起了這個:

const int *p1; /* p1所指向的int變量值不可改變,為常量,但可以改變p1指針的值 */ int * const p2; /* p2指針為常量,即p2的值不可改變,但可以改變p2指向對象的值 */ const int * const p3; /* p3指針是常量,同時p3所指向int對象的值也是常量 */


啰啰嗦嗦說這么長一個例子,其實很簡單的道理,就是為了好玩,好記。先宏觀的說一下const常量的實現機制,下面說一些具體的實現。


其實女票提出的問題不算難,是C++語言的一個知識點,總而言之就是一個常量折疊。

先說一個錯誤的理解(為什么要說一個錯誤的理解,因為它有助于正確的理解,哈哈):可折疊的常量就像宏一樣,在預編譯階段對const常量的引用一律被替換為常量所對應的值。就和普通的宏替換沒有什么區別,并且編譯器不會為該常量分配存儲空間。

看清楚了,上面說的是一個錯誤的理解,常量折疊確實會像宏替換一樣把對常量的引用替換為常量對應的值,但是該常量是分配空間的,并且靠編譯器來檢查常量屬性。

#define PI 3.14 int main() { ??? const?int r = 10; ??? ??? int p = PI;//這里在預編譯階段產生宏替換,PI直接替換為3.14,其實就是int p = 3.14 ??? int len = 2 * r;//這里會發生常量折疊,也就是說常量r的引用會替換成它對應的值,相當于int len = 2 * 10; ??? return 0; }

如上述代碼中所述,常量折疊表面上的效果和宏替換是一樣的,只是,“效果上是一樣的”,而兩者真正的區別在于,宏是字符常量,在預編譯階段的宏替換完成后,該宏名字會消失,所有對宏如PI的引用已經全部被替換為它所對應的值,編譯器當然沒有必要再維護這個符號。而常量折疊發生的情況是,對常量的引用全部替換為該常量如r的值,但是,常量名r并不會消失,編譯器會把他放入到符號表中,同時,會為該變量分配空間,棧空間或者全局空間。既然放到了符號表中,就意味著可以找到這個變量的地址(埋一個伏筆先)。

符號表不是一張表,是一系列表的統稱,這里的const常量,會把這個常量的名字、類型、內存地址、都放到常量表中。符號表還有一個變量表,這個表放變量的名字、類型、內存地址,但是沒有放變量的值。

為了更能體現出常量折疊,看下面的對比實驗:

int main() {int i0 = 11;const int i = 0; //定義常量iint *j = (int *) &i; //看到這里能對i進行取值,判斷i必然后自己的內存空間*j = 1; //對j指向的內存進行修改printf("0x%p\n0x%p\n%d\n%d\n",&i,j,i,*j); //觀看實驗效果const int ck = 9; //這個對照實驗是為了觀察,對常量ck的引用時,會產生的效果int ik = ck;int i1 = 5; //這個對照實驗是為了區別,對常量和變量的引用有什么區別int i2 = i1;return 0; } 下面看一下不同編譯器的輸出結果

vc6.0:

vs2010:

g++的輸出結果:

注意:對于Linux的GUN中的gcc是用來編譯.c文件的C語言編譯器,g++是用來編譯.cpp的C++語言編譯器。

我們這里講的是C++的商量折疊,所以源文件要是.cpp的才可以。(C語言的const常量最后再說)


上面的程序的運行結果至少說明兩點:

(1)i和j地址相同,指向同一塊空間,i雖然是可折疊常量,但是,i確實有自己的空間

(2)i和j指向同一塊內存,但是*j = 1對內存進行修改后,按道理來說,*j==1,i也應該等于1,而實驗結果確實i實實在在的等于0。
這是為什么呢,就是本文所說的內容,i是可折疊常量,在編譯階段對i的引用已經別替換為i的值了,同時不同于宏替換的是,這個i還被存到了常量表中。
也就是說:

printf("0x%p\n0x%p\n%d\n%d\n",&i,j,i,*j);

中的i在預處理階段已經被替換,其實已經被改為:

printf("0x%p\n0x%p\n%d\n%d\n",&i,j,0,*j);

同時在常量表中也有i這個變量,不然的話對i取地址是不合法的,這是和宏替換的不同點,宏替換是不會把宏名稱放到常量表中的,預編譯完就用不到了。

為了更加直觀,下面直接上這個程序的反編譯的匯編語言:

(1)方法:打個斷點調試->窗口反匯編(還有好多功能,比如內存多線程等)

(2)反匯編代碼:

1 --- d:\cv_projects\commontest\commontest\commontest.cpp ------------------------ 2 #include <stdio.h> 3 int main() 4 { 5 01311380 push ebp 6 01311381 mov ebp,esp 7 01311383 sub esp,114h 8 01311389 push ebx 9 0131138A push esi 10 0131138B push edi 11 0131138C lea edi,[ebp-114h] 12 01311392 mov ecx,45h 13 01311397 mov eax,0CCCCCCCCh 14 0131139C rep stos dword ptr es:[edi] 15 int i0 = 11; 16 0131139E mov dword ptr [i0],0Bh 17 18 const int i = 0; //定義常量i 19 013113A5 mov dword ptr [i],0 //編譯器確實為常量i分配了棧空間,并賦值為0 20 int *j = (int *) &i; //看到這里能對i進行取值,判斷i必然后自己的內存空間 21 013113AC lea eax,[i] 22 013113AF mov dword ptr [j],eax 23 *j = 1; //對j指向的內存進行修改 24 013113B2 mov eax,dword ptr [j] 25 013113B5 mov dword ptr [eax],1 26 printf("0x%p\n0x%p\n%d\n%d\n",&i,j,i,*j); //觀看實驗效果 27 013113BB mov esi,esp 28 013113BD mov eax,dword ptr [j] 29 013113C0 mov ecx,dword ptr [eax] 30 013113C2 push ecx 31 013113C3 push 0 32 013113C5 mov edx,dword ptr [j] 33 013113C8 push edx 34 013113C9 lea eax,[i] 35 013113CC push eax 36 013113CD push offset string "0x%p\n0x%p\n%d\n%d\n" (131573Ch) 37 013113D2 call dword ptr [__imp__printf (13182B0h)] 38 013113D8 add esp,14h 39 013113DB cmp esi,esp 40 013113DD call @ILT+295(__RTC_CheckEsp) (131112Ch) 41 42 const int ck = 9; //這個對照實驗是為了觀察,對常量ck的引用時,會產生的效果 43 013113E2 mov dword ptr [ck],9 //為常量分配棧空間,這是符號表中已經有了這個變量 44 int ik = ck; 45 013113E9 mov dword ptr [ik],9 //看到否,對常量ck的引用,會直接替換為常量的值9,這種替換很類似于宏替換,再看下面的實驗 46 47 int i1 = 5; //這個對照實驗是為了區別,對常量和變量的引用有什么區別 48 013113F0 mov dword ptr [i1],5 49 int i2 = i1; //這里引用變量i1,對i2進行賦值,然后看到否,對常量i1引用沒有替換成i1的值,而是去棧中先取出i1的值,到edx寄存器中,然后再把值mov到i2所在的內存中 50 013113F7 mov eax,dword ptr [i1] 51 013113FA mov dword ptr [i2],eax 52 53 return 0; 54 013113FD xor eax,eax 55 } View Code

通過上述實驗的分析可以容易看出,對可折疊的常量的引用會被替換為該常量的值,而對變量的引用就需要訪問變量的內存。

總結:常量折疊說的是,在編譯階段,對該變量進行值替換,同時,該常量擁有自己的內存空間,并非像宏定義一樣不分配空間,需澄清這點


前面說了個不許用gcc編譯.c的C語言的程序,不然就沒有了常量折疊的問題,先看一下執行的結果:

最后說一下gcc編譯的C語言的const常量,這里并沒有做常量折疊的這種優化,類似于const常量前面加上volatile這個關鍵字。具體是怎么回事?下一篇博客再說。

轉載于:https://www.cnblogs.com/stemon/p/4406824.html

總結

以上是生活随笔為你收集整理的C++的常量折叠(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。