網站首頁 編程語言 正文
一、結論
聲明:不同于C語言的const變量修改問題(可以通過指針間接修改const變量的值),這里只討論C++ 里的const。
C++ const 修飾符,表示常量,即如果以后保證不會修改則聲明為const,否則若要修改,那一開始為什么還要聲明為const呢?
根據C++標準,對于修改const變量,屬于:未定義行為(指行為不可預測的計算機代碼),這樣一來此行為取決于各種編譯器的具體實現(即不同編譯器可能表現不同)。
故結論就是:不建議這么做!
但是,是的,但是,網上論壇、博客里均有有關如何修改const變量的方法,其不是依賴于某種具體的編譯器,就是講的欠考慮。
方法是在定義變量的時候加上volatile關鍵字(沒有其他方法了嗎(比如,const_cast ...)? 是的,目前為止,我只知道這種方法是可能的):
const volatile int i = 10;
注:關于volatile這里不細講,詳見:volatile 關鍵字。考慮到volatile的重要性,后面自己也會寫一篇關于volatile詳解的文章。
二、分析
為了說明問題,下面在三種編譯器環境下做幾個小實驗
1. g++?
#includeint main() { const volatile int i = 10; int* pi = (int*)(&i); *pi = 100; printf("*pi: %d\n",*pi); printf("i: %d\n",i); printf("pi: %p\n",pi); printf("&i: %p\n", &i); return 0; }
輸出結果:
gdb查看其匯編代碼(命令:進入gdb,然后輸入:disass main):
可以看出:輸入*pi 和 i 時均是從堆棧(即內存)中取數的。
反例:把 volatile關鍵字去掉:
#includeint main() { const int i = 10; int* pi = (int*)(&i); *pi = 100; printf("*pi: %d\n",*pi); printf("i: %d\n",i); printf("pi: %p\n",pi); printf("&i: %p\n", &i); return 0; }
輸出結果:
由此可見:在沒有volatile關鍵字修飾時,const 變量 i 的值時沒有改變的。
運用gdb查看其匯編代碼:
注意此時(沒有加volatile修飾符),輸出 變量 i 的值時直接將 0xa(10)值(從符號表中取出的)輸出,即此處編譯器進行了優化,沒有從內存中讀。
注意到:指針 pi 和 &i(i 的地址)值卻是一樣的。So ,Why?
這就是C++中的常量折疊:指const變量(即常量)值放在編譯器的符號表中,計算時編譯器直接從表中取值,省去了訪問內存的時間,從而達到了優化。
而在此基礎上加上volatile修改符,即告訴編譯器該變量屬于易變的,不要對此句進行優化,每次計算時要去內存中取數。
這里也有個小細節:每種編譯器對volatile修飾符的修飾作用效果不一致,有的就直接“不理會”,如VC++6.0編譯器(下面會講到)。
2. dev c++
運行結果與1(g++)一致。
3. VC++ 6.0
(1)添加volatile修飾符時,輸出結果(程序代碼同上):
?i 的值還是10,沒有改變!這是為什么呢?不急,先看下其匯編代碼:
注意:g++ 匯編代碼的mov指令 與 VC++ 6.0的mov指令不同(傳送方向相反)。
真相大白:雖然定義const變量的同時加上了volatile修飾符,但VC++ 6.0編譯器還是進行了優化措施,輸出 i 時 從編譯器的符號表中取值,直接輸出。
(2)無 volatile 修飾符時。輸出結果:
i 的值沒有改變,預期中。其匯編代碼為:
結果與添加volatile時相同。
即在VC++6.0編譯環境下,在const變量定義時添加volatile修飾符,與不添加效果是一樣的。編譯器都采取了優化(甚至把編譯器優化選項關閉還是如此,有點恐怖...)。
4. VS 2010
再看下Microsoft編譯器家族的高級版本:
(1)添加 volatile 修飾符時,輸出結果:
?i 的值被成功修改了!
(2)無 volatile 修飾符時,輸出結果:
i 的值沒有被修改。
故:不建議修改const變量的值,即使修改也要熟悉當前使用的編譯器對于該 未定義行為 是如何解釋的。
總結
原文鏈接:https://blog.csdn.net/heyabo/article/details/8745942
相關推薦
- 2022-05-12 C++深淺拷貝和string類的兩種寫法詳解_C 語言
- 2022-06-12 QT?.pro文件使用解析_C 語言
- 2022-08-25 Python面向對象的三大特性封裝、繼承、多態_python
- 2022-04-24 解決redis在linux上的部署的問題_Redis
- 2022-08-06 C語言繪制簡單時鐘小程序_C 語言
- 2022-04-08 swift實現簡易計算器項目_Swift
- 2023-03-21 C#實體類轉換的兩種方式小結_C#教程
- 2022-11-22 GoLang?channel關閉狀態相關操作詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支