• <tt id="lutwa"></tt>

    <b id="lutwa"></b>

  • <u id="lutwa"><small id="lutwa"></small></u>
    <b id="lutwa"><small id="lutwa"></small></b>
    
    
    <u id="lutwa"><small id="lutwa"></small></u>
  • <u id="lutwa"><small id="lutwa"></small></u>
    更多課程 選擇中心

    C/C++培訓
    達內IT學院

    400-111-8989

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

    • 發布:C++培訓
    • 來源:學習筆記
    • 時間:2020-05-14 16:15

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

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

    1)在函數內部通過malloc或new在堆上分配內存,然后把這塊內存返回(因為在堆上分配的內存是全局可見的)。這樣帶來的問題就是潛在的內存問題。因為,如果返回出去的內存不釋放,那么就是memory Leak?;蛘呤潜欢啻吾尫?,從而造成程序的crash。這兩個問題都相當的嚴重,所以這種設計方法并不推薦。(在一些Windows API中,當你調用了一些API后,你必需也要調用他的某些API來釋放這塊內存)

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

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

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

    示例

    有過socket編程經驗的人一定知道一個函數叫:inet_ntoa,這個函數主要的功能是把一個數字型的IP地址轉成字符串,這個函數的定義是這樣的(注意它的返回值):

    char *inet_ntoa(struct in_addr in);

    顯然,這個函數不會分配堆上的內存,而他又沒有讓你傳一下字符串的buffer進入,那么他一定使用“返回static char[]”這種方法。在我們繼續我們的討論之前,讓我們先了解一下IP地址相關的知識,下面是inet_ntoa這個函數需要傳入的參數:(也許你會很奇怪,只有一個member的struct還要放在struct中干什么?這應該是為了程序日后的擴展性的考慮)

    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變量進行了討論。結果也是持否定態度的。

    版權聲明:轉載文章來自公開網絡,版權歸作者本人所有,推送文章除非無法確認,我們都會注明作者和來源。如果出處有誤或侵犯到原作者權益,請與我們聯系刪除或授權事宜。

    預約申請免費試聽課

    填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

    上一篇:C與C++的內存機制的比較
    下一篇:C++ shared_ptr和動態數組

    C語言創建windows窗口實例

    C++回調函數是什么?

    C++ shared_ptr和動態數組

    C語言有哪些關鍵詞,C語言44個關鍵詞大全

    • 關注微信公眾號

      回復關鍵字:視頻資料

      免費領取 達內課程視頻學習資料

    • 視頻學習QQ群

      添加QQ群:1143617948

      免費領取達內課程視頻學習資料

    Copyright ? 2018 Tedu.cn All Rights Reserved 京ICP備08000853號-56 京公網安備 11010802029508號 達內時代科技集團有限公司 版權所有

    選擇城市和中心
    黑龍江省

    吉林省

    河北省

    湖南省

    貴州省

    云南省

    廣西省

    海南省

    欧美牲交av欧美牲交aⅴ暴力,俄罗斯freeⅹ性欧美,日本加勒比无码中文字幕,放荡的女教师中文字幕