字串常量

sunmenggmail發表於2012-04-26

我相信,使用C/C++多年的人對下面這個字串賦值語句都不會陌生吧。
 
              char* p = "test";
 
同時,我也相信,各位在使用這種語句後吃過很多苦頭也不少吧?只要你想利用指標p來改變字串的內容,你的程式都會得到一個讓你顏面盡失一個記憶體非法操作。比如,下面的這些語句:
 
              p[0] = 's';
              strcpy(p, "haoel");
 
原因就在於,char* p = "test"; 這個宣告,宣告瞭一個指標,而這個指標指向的是全域性的const記憶體區const記憶體區當然不會讓你想改就改的。所以,如果你一定要寫這塊記憶體的話,那就是一個非常嚴重的記憶體錯誤。另,之所以加粗“全域性const記憶體區”,是強調一下,如果你不信的話,你可以試試下面這段程式碼,看看p1p2的地址是不是一樣的。
 
              char* p1 = "anything";
              char* p2 = "anything";
              printf(“ p1=%x, p2=%x /n”, p1, p2);
 
我想這應該是一個眾所周知的問題吧。取而代之的,應該是使用陣列來做初始化宣告。如:char str[] = “hello world”; 如果現在還有哪本書中的C的示例採用了使用const字串初始化指標的這種方式,那麼你就可以把那本書撕了,如果這本書是C++的書話,那麼你應該把這個作者和這個出版社告上法庭,因為你不應該容忍這種學術騙子。如果你的部門的開發人員還有人寫出這種程式碼的話,如果他是C程式設計師,我想你可以在打過他的屁股後告訴他下不為例,如果他是一個C++程式設計師的話,我想你可以懷疑他是否有資格做一個C++程式設計師了。
 
       至於你問我為什麼要對學C++的人那麼苛刻,那是因為學過C++的人都知道C++中的const關鍵字的有著什麼樣的權力,你也應該知道C++const有著無比的照顧和關愛,幾乎所有關於C++的書都會提到const這東西。所以,如果作為一個C++的程式設計師來說,如果你不知道的話,那就太說不過去了。
 
       我們知道,雙引號引起來的字串是const的,所以,在C++的世界中,你應該進行如下的宣告才比較穩妥:
             
              const char *p = "test";
 
這樣,當你修改這個字串的內容時,編譯器會給你一個錯誤而導致你的程式編譯不通過,從而不會產生執行時的記憶體錯誤。
 
       可問題是,像C++這種對型別要求很嚴格的語言來說,為什麼它在編譯諸如char *p="test" 程式的時候不出錯,甚至連個警告都沒有(g++vc++7)?難道這是他的一個bug?我想,這應該是對古老的C的一個向下相容。因為,在C的世界中,這種用法太多了。
 
C++中,比如:函式的引數和異常的捕獲都存在這種問題,如下所示:(因編譯器而定,在gcc 3.4.3版中,下例中的異常示例不能被捕獲,但VC++6中卻可以被捕獲)
 
       func( char* p) { }   // 以這種方式呼叫函式func(“abc”);
      
       try { thow “exception”; } catch (char* p) { }
 
       這些都是C++編譯器預設了可以把const char* 轉成 char* 的罪行,無疑會對大家是一個誤導。甚至讓人無所畏懼地走入其中,並自以為走入了正途。這樣看來,這種向下相容的C++標準,就顯得有點誤人不淺了
 
       不過好在,C++標準委員會早已意識到了這一點。這個C++feature被定義為了“Deprecated Feature”,即“不被建議使用的特性”。意思就是,在將來,這種特性將被從C++中移出,於是,你目前的這種程式將無法在新的C++編譯器上編譯通過。對於程式的可移植性來說,我們今天所寫的程式碼尤其要注意這些“Deprecated Feature”。
 
       據我所知,目前C++中被列為“Deprecated Feature”如下所示(可能不準確,請大家指正)下面的這些feature都已被C++標準委員會訂為廢除featrue了。
 
一、           隱晦的字串的const轉換。

char *p = "test";
w_char *pw = L"test";

       把一個const的字串型別轉成non-const的。包括指標和陣列。
 
二、           隱晦的型別宣告。

func() {}   //函式的隱晦返回型別是int
static num;    //變數的隱晦型別是int
       這種featureC89中還可以使用,但在C99C++中都被去除了。(gcc 3.4版本對於這種宣告會給出編譯錯誤,而VC++6.0會認為這是合法的程式)
 
三、           布林變數的累加操作。
bool isConn = false;
isConn++;          //這個操作會把isConn變為true
就目前而言,幾乎所有的編譯器都認可這種操作,但這種用法也是不被建議的,終有一天會被取消。
 
四、           更改父類成員的存取許可權。
 
class B
{
    protected:
        int i;
};
 
class D : public B
{
     public:
        B::i; //這種方式可能大家很少看到。
};
 
       對於這種語法,子類重新暴露了父類的私有成員。這會帶來很大的安全性問題。目前而言,這個feature對於所有的編譯器來說應該都是可以編譯通過的(連個Warning都沒有)。但這個feature也是要被廢除的。
 
五、           檔案中域的static宣告
 
static int i;
static void func()
 
       據說,這種舊的在C中的為了實現其作用域在本檔案中的feature在未來的C++中也要被取消。
 
 
文章到這裡應該結束了,在結束之前,讓我再給大家共享一個有趣的關於const的例子(在網上看到的)
 
    const int a = 1;
    int *p = const_cast<int*>(&a);
    *p = 2;
 
    cout << “value a=”<< a << endl;
    cout << “value *p=” <<*p << endl;
    cout << “address a=” <<&a << endl;
    cout << “address p=” <<p << endl;
這段程式碼輸出的結果如下:
 
value a=1
value *p=2
address a=0xbff1d48c
address p=0xbff1d48c
 
地址都是一樣的,可值為什麼不一樣呢?呵呵。這個問題看起來有點“學術味”過濃,不過是個好例子,可以讓你知道C++的一些用法和一些原理。有以下幾個方面大家可以考慮一下:
1)const int a = 1是不是和巨集有點像,會不會被編譯器優化了?
2)去修改一個const的值,本來應該是不對的。這可能會是向舊的C相容。是否會讓編譯器產生未知行為?
 
所以,這個示例也告訴我們,我們應該遵循C++中的constnon-const的語義,任何想要破壞這個語義的事情都會給我們帶來未知的結果。

相關文章