在咱們用C/C++開發的過程當中,老是有一個問題會給咱們帶來苦惱。這個問題就是函數內和函數外代碼須要經過一塊內存來交互(好比,函數返回字符串),這個問題困擾和不少開發人員。若是你的內存是在函數內棧上分配的,那麼這個內存會隨着函數的返回而被彈棧釋放,因此,你必定要返回一塊函數外部還有效的內存。shell
這是一個讓無數人困擾的問題。若是你一不當心,你就頗有可能在這個上面犯錯誤。固然目前有不少解決方法,若是你熟悉一些標準庫的話,你能夠看到許多各式各樣的解決方法。大致來講有下面幾種:編程
1)在函數內部經過malloc或new在堆上分配內存,而後把這塊內存返回(由於在堆上分配的內存是全局可見的)。這樣帶來的問題就是潛在的內存問題。由於,若是返回出去的內存不釋放,那麼就是memory Leak。或者是被屢次釋放,從而形成程序的crash。這兩個問題都至關的嚴重,因此這種設計方法並不推薦。(在一些Windows API中,當你調用了一些API後,你必需也要調用他的某些API來釋放這塊內存)網絡
2)讓用戶傳入一塊他本身的內存地址,而在函數中把要返回的內存放到這塊內存中。這是一個目前廣泛使用的方式。不少Windows API函數或是標準C函數都須要你傳入一個buffer和這個buffer的長度。這種方式對咱們來講應該是家常便飯了。這種方式的好處就是由函數外部的程序來維護這塊內存,比較簡顯直觀。但問題就是在使用上稍許有些麻煩。不過這種方式把犯錯誤的機率減到了最低。socket
3)第三種方式顯得比較另類,他利用了static的特性,static的棧內存一旦分配,那這塊內存不會隨着函數的返回而釋放,並且,它是全局可見的(只要你有這塊內存的地址)。因此,有一些函數使用了static的這個特性,即不用使用堆上的內存,也不須要用戶傳入一個buffer和其長度。從而,使用得本身的函數長得很漂亮,也很容易使用。函數
這裏,我想對第三個方法進行一些討論。使用static內存這個方法看似不錯,可是它有讓你想象不到的陷阱。讓咱們來用一個實際發生的案例來舉一個例子吧。spa
有過socket編程經驗的人必定知道一個函數叫:inet_ntoa,這個函數主要的功能是把一個數字型的IP地址轉成字符串,這個函數的定義是這樣的(注意它的返回值):設計
char *inet_ntoa(struct in_addr in);調試
顯然,這個函數不會分配堆上的內存,而他又沒有讓你傳一下字符串的buffer進入,那麼他必定使用「返回static char[]」這種方法。在咱們繼續咱們的討論以前,讓咱們先了解一下IP地址相關的知識,下面是inet_ntoa這個函數須要傳入的參數:(也許你會很奇怪,只有一個member的struct還要放在struct中幹什麼?這應該是爲了程序往後的擴展性的考慮)code
struct in_addr {
unsigned long int s_addr;
}
對於IPV4來講,一個IP地址由四個8位的bit組成,其放在s_addr中,高位在後,這是爲了方便網絡傳輸。若是你獲得的一個s_addr的整型值是:3776385196。那麼,打開你的Windows計算器吧,看看它的二進制是什麼?讓咱們從右到左,8位爲一組(以下所示)。對象
11100001 00010111 00010000 10101100
再把每一組轉成十進制,因而咱們就獲得:225 23 16 172, 因而IP地址就是 172.16.23.225。
好了,言歸正傳。咱們有這樣一個程序,想記錄網絡包的源地址和目地地址,因而,咱們有以下的代碼:
struct
in_addr src, des;
........
........
fprintf
(fp,
"源IP地址<%s>/t目的IP地址<%s>/n"
, inet_ntoa(src), inet_ntoa(des));
會發生什麼樣的結果呢?你會發現記錄到文件中的源IP地址和目的IP地址徹底同樣。這是什麼問題呢?因而你開始調試你的程序,你發現src.s_addr和des.s_addr根本不同(以下所示)。可爲何輸出到文件的源和目的都是同樣的?難道說是inet_ntoa的bug?
src.s_addr = 3776385196;
//對應於172.16.23.225
des.s_addr = 1678184620;
//對應於172.16.7.100
緣由就是inet_ntoa()「自做聰明」地把內部的static char[]返回了,而咱們的程序正是踩中了這個陷阱。讓咱們來分析一下fprintf代碼。在咱們fprintf時,編譯器先計算inet_ntoa(des),因而其返回一個字符串的地址,而後程序再去求inet_ntoa(src)表達式,又獲得一個字符串的地址。這兩個字符串的地址都是inet_ntoa()中那個static char[],顯然是同一個地址,而第二次求src的IP時,這個值的des的IP地址內容必將被src的IP覆蓋。因此,這兩個表達式的字符串內存都是同樣的了,此時,程序會調用fprintf把這兩個字符串(實際上是一個)輸出到文件。因此,獲得相同的結果也就不奇怪。
仔細看一下inet_ntoa的man,咱們能夠看到這句話:The string is returned in a statically allocated buffer, which subsequent calls will overwrite. 證明了咱們的分析。
讓咱們你們都捫心自問一下,咱們在寫程序的過程中是否使用了這種方法?這是一個比較危險,容易出錯的方法。這種陷阱讓人防不勝防。想一想,若是你有這樣的程序:
if ( strcmp( inet_ntoa(ip1), inet_ntoa(ip2) )==0 ) {
…. ….
}
本想判斷一下兩個IP地址是否同樣,卻不料掉入了那個陷阱——讓這個條件表達式永真。
這個事情告訴咱們下面幾個道理:
1)慎用這種方式的設計。返回函數內部的static內存有很大的陷阱。
2)若是必定要使用這種方式的話。你就必須嚴肅地告訴全部使用這個函數的人,千萬不要在一個表達式中屢次使用這個函數。並且,還要告訴他們,不copy函數返回的內存的內容,而只是保存返回的內存地址或是引用是沒用的。否則的話,後果概不負責。
3)C/C++是很危險的世界,若是你不清楚他的話。仍是回火星去吧。
附:看過Efftive C++的朋友必定知道其中有一個條款(item 23):不要試圖返回對象的引用。這個條款中也對是否返回函數內部的static變量進行了討論。結果也是持否認態度的。