首先來看一個例子:
int main(int argc, char* argv[])
{
const int i=0;
int *j = (int *) &i;
*j=1;
cout<<&i<<endlcout<<j<<endl;
cout<<i<<endl;
cout<<*j<<endl;
return 0;
}
結果是
0012ff7c
0012ff7c
0
1
因為i和j都指向相同的內存地址,所以輸出的前兩個結果是相同的,但為啥相同的內存里的結果不相同么?--這就是常量折疊.
這個"常量折疊"是 就是在編譯器進行語法分析的時候,將常量表達式計算求值,并用求得的值來替換表達式,放入常量表。可以算作一種編譯優化。
我只是改了這個地址內容,但是i還是0,
因為編譯器在優化的過程中,會把碰見的const全部以內容替換掉(跟宏似的: #define pi 3.1415,用到pi時就用3.1415代替),這個出現在預編譯階段;但是在運行階段,它的內存里存的東西確實改變了!!!
6.網上的一些問題(4)
關于常量
這些天被常量的一些概念折磨著,現在終于有些明白了,
問題始于const int i = 10;//i存在哪,10存在哪
說明一:符號表
這個語句是對i的聲明,因為編譯器一開始就知道i的值,所以以后出現i時就會用10代替,這好像叫做符號表的概念,i就對應10了。
網上一篇帖子上有這樣的代碼:
const int a = 3;
int *p = const_cast<int *>(&a);
*p = 4;
cout << a;//仍然輸出3
這個結果可以用上面的說明來解釋
說明二:常量折疊(const folding)與復寫傳播 (copy propagation)
網上人們普遍反映thinking in c++將const folding譯為常量折疊是種誤導,我覺得譯的還行,本來folding就有折疊的意思,就是把原來的東西變小,而象const int i = 2*2;編譯器確實將2*2算成4了,以后碰到i就用4替換,這個計算2*2的過程據說叫常量折疊--const folding,而用4替換i的過程叫做復寫傳播--copy propagation.他們都是編譯器的優化技術
說明三:為常量分配空間
補充一下,這里說的都是const 定義的常量,而非文字常量,
(c++ primer翻譯成文字常量--literal constant
the c++ programming language(tcpl)翻譯成文字量,還分了不同類型)
至于文字常量存在哪,c++ primer 3ed上說它們是不可尋址的--nonaddressable,盡管它們也存在機器內存某個地方,但無法訪問它們的地址
對于int double等類型還好理解,但是對于字符串常量(tcpl里說將字符串文字量作為常量,利于存儲與訪問時的優化)下面的代碼似乎表示字符串常量存儲在靜態存儲區里(字符串文字量是靜態分配的--tcpl),那么字符串常量的地址不是可以訪問了嗎,在靜態存儲區里
http://bbs.bc-cn.net/dispbbs.asp?boardid=56&replyi...
字符串文字量的類型是常量字符數組--適當個數的const字符的數組
//有關字符常量的存儲區的問題
//另外,char a[]和char *a的區別
//"hello world 1"存在哪
#include <iostream>
using namespace std;
int main()
{
char* p = "hello world1";
char a[] = "hello world2";
//會為a在棧上分配13個字節的空間
// p[2] = a;
a[2] = a;
char* p1 = "hello world1"
printf("%xn",&p[2]);//p應該指向常量區
printf("%x",&a[2]);//棧上數組第三個元素的地址
return 0;
//結果42f036 //這是常量區
//12ff6e果然不一樣,這是棧區
}
6.總結
那么"常量折疊"到底是啥意思呢?
我理解,簡單的說就是,當編譯器處理const的時候,編譯器會將其變成一個立即數。
《thinking in c++》里面說這一點在數組定義里尤其重要(為啥呢?沒有查到相關的資料)。
版本:0.1
最后修改:2010-11-22
撰寫:李現民
const在c++中意味著“不可改變”,但在有些情況下我們可以“合法”地繞過編譯器去修改一些const數據,比如const_cast就可以剝離一個對象的const屬性。然而,我們這樣做在多大程度上是“合理”的,卻因不同的問題而論,也許一不小心,你就可能掉入陷阱之中。
以下問題,我只分析,不說話,請各位看官自己判斷。
這件事源于在網上看到的一篇文章,其來源已經不可考,但大意是:就如下C++程序,其輸出是什么:
void
foo()
{
const
int
a =
1;
int*
p =
const_cast<int*>(&a);
*p =
2;
printf("
a= %d\n *p= %d\n &a= %x\n p= %x \n\n",
a,
*p,
&a,
p);
}
我在VC2008下的實測結果為:
a = 1
*p = 2
&a = 12ff6c
p = 12ff6c
好了,問題出現:明明p所指向的就是變量a,但為何打印其值時a!=*p?
這并非是我用錯了const_cast,也不是編譯器進行了優化的問題。事實上,在各版本的VC與g++下的運行結果均是如此。
以下是VC2008下debug版本的匯編代碼:
const
int a = 1;
0041146E
mov dword ptr [a],1
int*
p = const_cast<int*>(&a);
00411475
lea eax,[a]
00411478
mov dword ptr [p],eax
*p =
2;
0041147B
mov eax,dword ptr [p]
0041147E
mov dword ptr [eax],2
printf("
a= %d\n *p= %d\n &a= %x\n p= %x \n\n", a, *p, &a, p);
00411484
mov esi,esp
00411486
mov eax,dword ptr [p]
00411489
push eax
0041148A
lea ecx,[a]
0041148D
push ecx
0041148E
mov edx,dword ptr [p]
00411491
mov eax,dword ptr [edx]
00411493
push eax
00411494
push 1
00411496
push offset string " a=\t%d\n *p=\t%d\n &a=\t%x\n
p=\t%x \n\n"... (415808h)
0041149B
call dword ptr [__imp__printf (419318h)]
從printf()的四個參數入棧過程中我們可以看出:指針p的確指向變量a了,而變量a處的數值也的確被改寫成2了,問題是:當壓入a的值的時候,編譯器直接壓入了其原始數值1。
關鍵其實在于:const_cast所操作的目標是否為基礎數據類型(char, int, float, double等),如果是類(或結構體)對象則又將是另一番情形。
這個問題最早見于一篇文章《Solmyr的小品文系列之一:字符串放在哪里?》,在這里我只不過轉述一二。
代碼如下:
void
foo()
{
char*
str1 =
"watch";
const
char*
str2 =
"watch";
char
str3[] =
"watch";
str1[0] =
'm';
std::cout<<
str1
<< std::endl
<< str2
<< std::endl
<< str3
<< std::endl;
}
VC6中Release版本運行結果如下:
match
match
watch
VC2008中Release版本運行結果如下:
watch
watch
watch
容易看出:這段代碼的運行結果決定于編譯器,因為我們改寫了不應該被改寫的常量數據。更根本的原因是:由于編譯器優化,str1與str2實際上指向的是同一份”watch”字符串。
這還帶出了另一件事:盡管str1的聲明中不帶const,但它所指向的字符串數據隱含是const類型的。
注意:這段代碼只有Release版本才能順利執行,Debug版版本運行時會得到一個Access violation 。
delete this--對象請求自殺
版本:0.1
最后修改:2009-08-18
撰寫:李現民
第一次見delete this的時候,沒覺得這是一項會有什么特殊作用的技術,因此也就沒有特別關注。
昨日在sourcemaking.com上看state模式之c++實現的時候,看到它在狀態轉換的時候使用了delete this,感覺似乎還不錯。
作為一種“禁術”,使用的時候必須要相當小心才行,《C++ FAQ》里提到所謂“小心”至少包括以下幾個方面:
this對象是必須是用 new操作符分配的(而不是用new[],也不是用placement new,也不是局部對象,也不是global對象);
delete this后,不能訪問該對象任何的成員變量及虛函數(delete this回收的是數據,這包括對象的數據成員以及vtable,不包括函數代碼);
delete this后,不能再訪問this指針。換句話說,你不能去檢查它、將它和其他指針比較、和 NULL比較、打印它、轉換它,以及其它的任何事情;
個人認為保證以上禁忌列表基本手段可以包括:
將析構函數私有化(如果有子類,則protected化,保證子類能夠正確繼承)--以保證對象必須使用new在堆上分配內存;
提供(可以在僅僅在基類中)Destroy(void)函數,里面僅有一句delete this--以保證第三方能夠將分配的內存回收;
下一次使用state pattern的時候,我想可以嘗試一下。