C C 返回內部靜態成員的陷阱

2021-06-10 20:45:28 字數 2758 閱讀 5815

在我們用c/c++開發的過程中,總是有乙個問題會給我們帶來苦惱。這個問題就是函式內和函式外**需要通過一塊記憶體來互動(比如,函式返回字串),這個問題困擾和很多開發人員。如果你的記憶體是在函式內棧上分配的,那麼這個記憶體會隨著函式的返回而被彈棧釋放,所以,你一定要返回一塊函式外部還有效的記憶體。

這是乙個讓無數人困擾的問題。如果你一不小心,你就很有可能在這個上面犯錯誤。當然目前有很多解決方法,如果你熟悉一些標準庫的話,你可以看到許多各式各樣的解決方法。大體來說有下面幾種:

1)在函式內部通過malloc或new在堆上分配記憶體,然後把這塊記憶體返回(因為在堆上分配的記憶體是全域性可見的)。這樣帶來的問題就是潛在的記憶體問題。

因為,如果返回出去的記憶體不釋放,那麼就是memory leak。或者是被多次釋放,從而造成程式的crash。這兩個問題都相當的嚴重,所以這種設計方法並不推薦。(在一些windows api中,當你呼叫了一些api後,你必需也要呼叫他的某些api來釋放這塊記憶體)

2)讓使用者傳入一塊他自己的記憶體位址,而在函式中把要返回的記憶體放到這塊記憶體中。這是乙個目前普遍使用的方式。很多windows api函式或是標準c函式都需要你傳入乙個buffer和這個buffer的長度。這種方式對我們來說應該是屢見不鮮了。這種方式的好處就是由函式外部的程式來維護這塊記憶體,比較簡顯直觀。但問題就是在使用上稍許有些麻煩。不過這種方式把犯錯誤的機率減到了最低。

3)第三種方式顯得比較另類,他利用了static的特性,static的棧記憶體一旦分配,那這塊記憶體不會隨著函式的返回而釋放,而且,它是全域性可見的(只要你有這塊記憶體的位址)。所以,有一些函式使用了static的這個特性,即不用使用堆上的記憶體,也不需要使用者傳入乙個buffer和其長度。從而,使用得自己的函式長得很漂亮,也很容易使用。

這裡,我想對第三個方法進行一些討論。使用static記憶體這個方法看似不錯,但是它有讓你想象不到的陷阱。讓我們來用乙個實際發生的案例來舉乙個例子吧。

示例

char *inet_ntoa(struct in_addr in);

顯然,這個函式不會分配堆上的記憶體,而他又沒有讓你傳一下字串的buffer進入,那麼他一定使用「返回static char」這種方法。在我們繼續我們的討論之前,讓我們先了解一下ip位址相關的知識,下面是inet_ntoa這個函式需要傳入的引數:(也許你會很奇怪,只有乙個member的struct還要放在struct中幹什麼?這應該是為了程式日後的擴充套件性的考慮)

struct in_addr 

對於ipv4來說,乙個ip位址由四個8位的bit組成,其放在s_addr中,高位在後,這是為了方便網路傳輸。如果你得到的乙個s_addr的整型值是:3776385196。那麼,開啟你的windows計算器吧,看看它的二進位制是什麼?讓我們從右到左,8位為一組(如下所示)。

11100001 00010111 00010000 10101100

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++是很危險的世界,如果你不清楚他的話。還是回火星去吧。

C C 返回內部靜態成員的陷阱

c c 返回內部靜態成員的陷阱 陳皓 背景 在我們用c c 開發的過程中,總是有乙個問題會給我們帶來苦惱。這個問題就是函式內和函式外 需要通過一塊記憶體來互動 比如,函式返回字串 這個問題困擾和很多開發人員。如果你的記憶體是在函式內棧上分配的,那麼這個記憶體會隨著函式的返回而被彈棧釋放,所以,你一定...

C C 返回內部靜態成員的陷阱

c c 返回內部靜態成員的陷阱 陳皓 背景 在我們用c c 開發的過程中,總是有乙個問題會給我們帶來苦惱。這個問題就是函式內和函式外 需要通過一塊記憶體來互動 比如,函式返回字串 這個問題困擾和很多開發人員。如果你的記憶體是在函式內棧上分配的,那麼這個記憶體會隨著函式的返回而被彈棧釋放,所以,你一定...

C C 返回內部靜態成員的陷阱

c c 返回內部靜態成員的陷阱 陳皓 背景 在 我們用c c 開發的過程中,總是有乙個問題會給我們帶來苦惱。這個問題就是函式內和函式外 需要通過一塊記憶體來互動 比如,函式返回字串 這個 問題困擾和很多開發人員。如果你的記憶體是在函式內棧上分配的,那麼這個記憶體會隨著函式的返回而被彈棧釋放,所以,你...