• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            陳碩的Blog

            C++ 工程實(shí)踐(1):慎用匿名 namespace

            匿名 namespace (anonymous namespace 或稱 unnamed namespace) 是 C++ 的一項(xiàng)非常有用的功能,其主要目的是讓該 namespace 中的成員(變量或函數(shù))具有獨(dú)一無二的全局名稱,避免名字碰撞 (name collisions)。一般在編寫 .cpp 文件時(shí),如果需要寫一些小的 helper 函數(shù),我們常常會(huì)放到匿名 namespace 里。muduo 0.1.7 中的 muduo/base/Date.ccmuduo/base/Thread.cc 等處就用到了匿名 namespace。

            我最近在工作中遇到并重新思考了這一問題,發(fā)現(xiàn)匿名 namespace 并不是多多益善。

            C 語言的 static 關(guān)鍵字的兩種用法

            C 語言的 static 關(guān)鍵字有兩種用途:

            1. 用于函數(shù)內(nèi)部修飾變量,即函數(shù)內(nèi)的靜態(tài)變量。這種變量的生存期長于該函數(shù),使得函數(shù)具有一定的“狀態(tài)”。使用靜態(tài)變量的函數(shù)一般是不可重入的,也不是線程安全的。

            2. 用在文件級(jí)別(函數(shù)體之外),修飾變量或函數(shù),表示該變量或函數(shù)只在本文件可見,其他文件看不到也訪問不到該變量或函數(shù)。專業(yè)的說法叫“具有 internal linkage”(簡言之:不暴露給別的 translation unit)。

            C 語言的這兩種用法很明確,一般也不容易混淆。

            C++ 語言的 static 關(guān)鍵字的四種用法

            由于 C++ 引入了 class,在保持與 C 語言兼容的同時(shí),static 關(guān)鍵字又有了兩種新用法:

            3. 用于修飾 class 的數(shù)據(jù)成員,即所謂“靜態(tài)成員”。這種數(shù)據(jù)成員的生存期大于 class 的對(duì)象(實(shí)體 instance)。靜態(tài)數(shù)據(jù)成員是每個(gè) class 有一份,普通數(shù)據(jù)成員是每個(gè) instance 有一份,因此也分別叫做 class variable 和 instance variable。

            4. 用于修飾 class 的成員函數(shù),即所謂“靜態(tài)成員函數(shù)”。這種成員函數(shù)只能訪問 class variable 和其他靜態(tài)程序函數(shù),不能訪問 instance variable 或 instance method。

            當(dāng)然,這幾種用法可以相互組合,比如 C++ 的成員函數(shù)(無論 static 還是 instance)都可以有其局部的靜態(tài)變量(上面的用法 1)。對(duì)于 class template 和 function template,其中的 static 對(duì)象的真正個(gè)數(shù)跟 template instantiation (模板具現(xiàn)化)有關(guān),相信學(xué)過 C++ 模板的人不會(huì)陌生。

            可見在 C++ 里 static 被 overload 了多次。匿名 namespace 的引入是為了減輕 static 的負(fù)擔(dān),它替換了 static 的第 2 種用途。也就是說,在 C++ 里不必使用文件級(jí)的 static 關(guān)鍵字,我們可以用匿名 namespace 達(dá)到相同的效果。(其實(shí)嚴(yán)格地說,linkage 或許稍有不同,這里不展開討論了。)

            匿名 namespace 的不利之處

            在工程實(shí)踐中,匿名 namespace 有兩大不利之處:

            1. 其中的函數(shù)難以設(shè)斷點(diǎn),如果你像我一樣使用的是 gdb 這樣的文本模式 debugger。
            2. 使用某些版本的 g++ 時(shí),同一個(gè)文件每次編譯出來的二進(jìn)制文件會(huì)變化,這讓某些 build tool 失靈。

            考慮下面這段簡短的代碼 (anon.cc):

               1: namespace
               2: {
               3:   void foo()
               4:   {
               5:   }
               6: }
               7:  
               8: int main()
               9: {
              10:   foo();
              11: }

            對(duì)于問題 1:

            gdb 的<tab>鍵自動(dòng)補(bǔ)全功能能幫我們?cè)O(shè)定斷點(diǎn),不是什么大問題。前提是你知道那個(gè)"(anonymous namespace)::foo()"正是你想要的函數(shù)。

            $ gdb ./a.out
            GNU gdb (GDB) 7.0.1-debian

            (gdb) b '<tab>
            (anonymous namespace)         __data_start                  _end
            (anonymous namespace)::foo()  __do_global_ctors_aux         _fini
            _DYNAMIC                      __do_global_dtors_aux         _init
            _GLOBAL_OFFSET_TABLE_         __dso_handle                  _start
            _IO_stdin_used                __gxx_personality_v0          anon.cc
            __CTOR_END__                  __gxx_personality_v0@plt      call_gmon_start
            __CTOR_LIST__                 __init_array_end              completed.6341
            __DTOR_END__                  __init_array_start            data_start
            __DTOR_LIST__                 __libc_csu_fini               dtor_idx.6343
            __FRAME_END__                 __libc_csu_init               foo
            __JCR_END__                   __libc_start_main             frame_dummy
            __JCR_LIST__                  __libc_start_main@plt         int
            __bss_start                   _edata                        main

            (gdb) b '(<tab>
            anonymous namespace)         anonymous namespace)::foo()

            (gdb) b '(anonymous namespace)::foo()'
            Breakpoint 1 at 0x400588: file anon.cc, line 4.

            麻煩的是,如果兩個(gè)文件 anon.cc 和 anonlib.cc 都定義了匿名空間中的 foo() 函數(shù)(這不會(huì)沖突),那么 gdb 無法區(qū)分這兩個(gè)函數(shù),你只能給其中一個(gè)設(shè)斷點(diǎn)?;蛘吣闶褂?文件名:行號(hào) 的方式來分別設(shè)斷點(diǎn)。(從技術(shù)上,匿名 namespace 中的函數(shù)是 weak text,鏈接的時(shí)候如果發(fā)生符號(hào)重名,linker 不會(huì)報(bào)錯(cuò)。)

            從根本上解決的辦法是使用普通具名 namespace,如果怕重名,可以把源文件名(必要時(shí)加上路徑)作為 namespace 名字的一部分。

            對(duì)于問題 2:

            把它編譯兩次,分別生成 a.out 和 b.out:

            $ g++ -g -o a.out anon.cc

            $ g++ -g -o b.out anon.cc

            $ md5sum a.out b.out
            0f7a9cc15af7ab1e57af17ba16afcd70  a.out
            8f22fc2bbfc27beb922aefa97d174e3b  b.out

            $ g++ --version
            g++ (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)

            $ diff -u <(nm a.out) <(nm b.out)
            --- /dev/fd/63  2011-02-15 22:27:58.960754999 +0800
            +++ /dev/fd/62  2011-02-15 22:27:58.960754999 +0800
            @@ -2,7 +2,7 @@
            0000000000600940 d _GLOBAL_OFFSET_TABLE_
            0000000000400634 R _IO_stdin_used
                              w _Jv_RegisterClasses
            -0000000000400538 t _ZN36_GLOBAL__N_anon.cc_00000000_E2CEEB513fooEv
            +0000000000400538 t _ZN36_GLOBAL__N_anon.cc_00000000_CB51498D3fooEv
            0000000000600748 d __CTOR_END__
            0000000000600740 d __CTOR_LIST__
            0000000000600758 d __DTOR_END__

            由上可見,g++ 4.2.4 會(huì)隨機(jī)地給匿名 namespace 生成一個(gè)惟一的名字(foo() 函數(shù)的 mangled name 中的 E2CEEB51 和 CB51498D 是隨機(jī)的),以保證名字不沖突。也就是說,同樣的源文件,兩次編譯得到的二進(jìn)制文件內(nèi)容不相同,這有時(shí)候會(huì)造成問題。比如說拿到一個(gè)會(huì)發(fā)生 core dump 的二進(jìn)制可執(zhí)行文件,無法確定它是由哪個(gè) revision 的代碼編譯出來的。畢竟編譯結(jié)果不可復(fù)現(xiàn),具有一定的隨機(jī)性。

            這可以用 gcc 的 -frandom-seed 參數(shù)解決,具體見文檔。

            這個(gè)現(xiàn)象在 gcc 4.2.4 中存在(之前的版本估計(jì)類似),在 gcc 4.4.5 中不存在。

            替代辦法

            如果前面的“不利之處”給你帶來困擾,解決辦法也很簡單,就是使用普通具名 namespace。當(dāng)然,要起一個(gè)好的名字,比如 boost 里就常常用 boost::detail 來放那些“不應(yīng)該暴露給客戶,但又不得不放到頭文件里”的函數(shù)或 class。

             

            總而言之,匿名 namespace 沒什么大問題,使用它也不是什么過錯(cuò)。萬一它礙事了,可以用普通具名 namespace 替代之。

            posted on 2011-02-15 22:55 陳碩 閱讀(6780) 評(píng)論(3)  編輯 收藏 引用

            評(píng)論

            # re: C++ 工程實(shí)踐(1):慎用匿名 namespace 2011-02-16 09:31 zuhd

            喜歡樓主這種寫作風(fēng)格,深入淺出,感覺像是以前高中時(shí)做證明題一樣,很清晰  回復(fù)  更多評(píng)論   

            # re: C++ 工程實(shí)踐(1):慎用匿名 namespace 2011-02-17 13:20 淘寶皇冠店

            很好12659965  回復(fù)  更多評(píng)論   

            # re: C++ 工程實(shí)踐(1):慎用匿名 namespace 2011-02-18 10:48 zwp

            首先,匿名空間最大的好處是不用擔(dān)心名字沖突。如果,你能保證現(xiàn)有的代碼或者以后的代碼都不會(huì)使用你的具名空間的話,當(dāng)然可以使用具名空間。而這也正是使用匿名空間的理由。

            具名空間是一種封閉、限制和警戒,意味著可能的沖突。
            匿名空間則像一個(gè)開放的獨(dú)立的組織,一定程度上代表著可擴(kuò)展。  回復(fù)  更多評(píng)論   


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            <2011年4月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            1234567

            導(dǎo)航

            統(tǒng)計(jì)

            常用鏈接

            隨筆分類

            隨筆檔案

            相冊(cè)

            搜索

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            日韩精品无码久久一区二区三| 精品午夜久久福利大片| 久久99国产亚洲高清观看首页| 免费久久人人爽人人爽av| 精品国产乱码久久久久软件| 香蕉aa三级久久毛片| 少妇无套内谢久久久久| 亚洲精品第一综合99久久| 狠狠色丁香久久婷婷综合图片| 亚洲七七久久精品中文国产| 久久久久久毛片免费看| 欧美日韩精品久久久免费观看| 日韩欧美亚洲综合久久影院Ds | 国产亚洲综合久久系列| 国产精品9999久久久久| 亚洲国产精品婷婷久久| 久久婷婷五月综合97色直播| 久久婷婷色香五月综合激情| 久久精品卫校国产小美女| 国产成人无码久久久精品一| 成人亚洲欧美久久久久 | 国产亚洲成人久久| 久久亚洲电影| 久久婷婷成人综合色综合| 一本大道久久a久久精品综合| 日本久久久精品中文字幕| 亚洲精品成人久久久| 性做久久久久久久| 成人a毛片久久免费播放| 99久久免费国产精品特黄| 国产精品久久久亚洲| 久久国产视频99电影| 色综合久久久久无码专区| 精品国产热久久久福利| 亚洲va中文字幕无码久久 | 99久久伊人精品综合观看| 亚洲天堂久久久| 久久精品国产91久久综合麻豆自制 | 亚洲午夜精品久久久久久浪潮| 无码久久精品国产亚洲Av影片| 久久se精品一区精品二区国产|