• <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>

            道。道。道

            安全特性不等于安全的特性

               :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理

            常用鏈接

            搜索

            •  

            最新評論

            CreateThread 是一個Win 32API 函數(shù),_beginthread 是一個CRT(C Run-Time)函數(shù),他們都是實(shí)現(xiàn)多線城的創(chuàng)建的函數(shù),而且他們擁有相同的使用方法,相同的參數(shù)列表。

            但是他們有什么區(qū)別呢?

            一般來說,從使用角度是沒有多大的區(qū)別的,CRT函數(shù)中除了signal()函數(shù)不能在CreateThread創(chuàng)建的線城中使用外,其他的CRT函數(shù)都可一正常使用,但是如果在CreateThread創(chuàng)建的線城中使用CRT函數(shù)的話,會產(chǎn)生一些Memory Leak.

            下面是摘自KB的原話:

            SUMMARY

            All C Run-time functions except the signal() function work correctly when used in threads created by the CreateThread() function. However, depending on what CRT functions are called, there may be a small memory leak when threads are terminated. Calling strlen(), for example, does not trigger the allocation of the CRT thread data-block, and calling malloc(), fopen(), _open(), strtok(), ctime(), or localtime() causes allocation of a CRT per-thread data-block, which may cause a memory leak.

            MORE INFORMATION

            The "Programming Techniques" manual supplied with Visual C++ 32-bit Edition states that using CreateThread() in a program that uses Libcmt.lib causes many CRT functions to fail. Actually, the only function that should not be used in a thread created with CreateThread() is the signal() function.

            There are two ways to create threads. One method involves using the CRT _beginthread() or _beginthreadex() (with Visual C++ 2.0 and later); the other method involves using the CreateThread() API. All CRT functions other than the signal() function work correctly in threads created with either _beginthread() or CreateThread(). However, there are some problems involved with using CRT functions in threads created with CreateThread().

            Threads that are created and terminated with the CreateThread() and ExitThread() Win32 API functions do not have memory that is allocated by the CRT for static data and static buffers cleaned up when the thread terminates. Some examples of this type of memory are static data for errno and _doserrno and the static buffers used by functions such as asctime(), ctime(), localtime(), gmtime(), and mktime(). Using CreateThread() in a program that uses the CRT (for example, links with LIBCMT.LIB) may cause a memory leak of about 70-80 bytes each time a thread is terminated.

            To guarantee that all static data and static buffers allocated by the CRT are cleaned up when the thread terminates, _beginthreadex() and _endthreadex() should be used when creating a thread. The _beginthreadex() function includes the same parameters and functionality as CreateThread().
            另外有個小小的測驗(yàn):
            ?用CreateThread 創(chuàng)建的線城能否被CRT函數(shù) _endthreadex() 關(guān)閉?
            ?
            ?
            CreateThread()和_beginthreadex()在Jeffrey的《Windows核心編程》中講的很清楚,應(yīng)當(dāng)盡量避免使用CreateThread()。
            事實(shí)上,_beginthreadex()在內(nèi)部先為線程創(chuàng)建一個線程特有的tiddata結(jié)構(gòu),然后調(diào)用CreateThread()。在某些非線程安全的CRT函數(shù)中會請求這個結(jié)構(gòu)。如果直接使用CreateThread()的話,那些函數(shù)發(fā)現(xiàn)請求的tiddata為NULL,就會在現(xiàn)場為該線程創(chuàng)建該結(jié)構(gòu),此后調(diào)用EndThread()時會引起內(nèi)存泄漏。_endthreadex()可以釋放由CreateThread()創(chuàng)建的線程,實(shí)際上,在它的內(nèi)部會先釋放由_beginthreadex()創(chuàng)建的tiddata結(jié)構(gòu),然后調(diào)用EndThread()。
            因此,應(yīng)當(dāng)使用_beginthreadex()和_endthreadex(),而避免使用CreateThread()和EndThread()。當(dāng)然,_beginthread()和_endthread()也是應(yīng)當(dāng)避免使用的。
            ?
            ?
            程序員對于Windows程序中應(yīng)該用_beginthread還是CreateThread來創(chuàng)建線程,一直有所爭論。本文將從對CRT源代碼出發(fā)探討這個問題。

            I. 起因

            今天一個朋友問我程序中究竟應(yīng)該使用_beginthread還是CreateThread,并且告訴我如果使用不當(dāng)可能會有內(nèi)存泄漏。其實(shí)我過去對這個問題也是一知半解,為了對朋友負(fù)責(zé),專門翻閱了一下VC的運(yùn)行庫(CRT)源代碼,終于找到了答案。

            II. CRT

            CRT(C/C++ Runtime Library)是支持C/C++運(yùn)行的一系列函數(shù)和代碼的總稱。雖然沒有一個很精確的定義,但是可以知道,你的main就是它負(fù)責(zé)調(diào)用的,你平時調(diào)用的諸如strlen、strtok、time、atoi之類的函數(shù)也是它提供的。我們以Microsoft Visual.NET 2003中所附帶的CRT為例。假設(shè)你的.NET 2003安裝在C:Program FilesMicrosoft Visual Studio .NET 2003中,那么CRT的源代碼就在C:Program FilesMicrosoft Visual Studio .NET 2003Vc7crtsrc中。既然有了這些實(shí)現(xiàn)的源代碼,我們就可以找到一切解釋了。

            III. _beginthread/_endthread

            這個函數(shù)究竟做了什么呢?它的代碼在thread.c中。閱讀代碼,可以看到它最終也是通過CreateThread來創(chuàng)建線程的,主要區(qū)別在于,它先分配了一個_tiddata,并且調(diào)用了_initptd來初始化這個分配了的指針。而這個指針最后會被傳遞到CRT的線程包裝函數(shù)_threadstart中,在那里會把這個指針作為一個TLS(Thread Local Storage)保存起來。然后_threadstart會調(diào)用我們傳入的線程函數(shù),并且在那個函數(shù)退出后調(diào)用_endthread。這里也可以看到,_threadstart用一個__try/__except塊把我們的函數(shù)包了起來,并且在發(fā)生異常的時候,調(diào)用exit退出。(_threadstart和endthread的代碼都在thread.c中)
            這個_tiddata是一個什么樣的結(jié)構(gòu)呢?它在mtdll.h中定義,它的成員被很多CRT函數(shù)所用到,譬如int _terrno,這是這個線程中的錯誤標(biāo)志;char* _token,strtok以來這個變量記錄跨函數(shù)調(diào)用的信息,...。
            那么_endthread又做了些什么呢?除了調(diào)用浮點(diǎn)的清除代碼以外,它還調(diào)用了_freeptd來釋放和這個線程相關(guān)的tiddata。也就是說,在_beginthread里面分配的這塊內(nèi)存,以及在線程運(yùn)行過程中其它CRT函數(shù)中分配并且記錄在這個內(nèi)存結(jié)構(gòu)中的內(nèi)存,在這里被釋放了。
            通過上面的代碼,我們可以看到,如果我使用_beginthread函數(shù)創(chuàng)建了線程,它會為我創(chuàng)建好CRT函數(shù)需要的一切,并且最后無需我操心,就可以把清除工作做得很好,可能唯一需要注意的就是,如果需要提前終止線程,最好是調(diào)用_endthread或者是返回,而不要調(diào)用ExitThread,因?yàn)檫@可能造成內(nèi)存釋放不完全。同時我們也可以看出,如果我們用CreateThread函數(shù)創(chuàng)建了線程,并且不對C運(yùn)行庫進(jìn)行調(diào)用(包括任何間接調(diào)用),就不必?fù)?dān)心什么問題了。

            IV. CreateThread和CRT

            或許有人會說,我用CreateThread創(chuàng)建線程以后,我也調(diào)用了C運(yùn)行庫函數(shù),并且也使用ExitThread退出了,可是我的程序運(yùn)行得好好的,既沒有因?yàn)镃RT沒有初始化而崩潰,也沒有因?yàn)橥浾{(diào)用_endthread而發(fā)生內(nèi)存泄漏,這是為什么呢,讓我們繼續(xù)我們的CRT之旅。
            假設(shè)我用CreateThread創(chuàng)建了一個線程,我調(diào)用strtok函數(shù)來進(jìn)行字符串處理,這個函數(shù)肯定是需要某些額外的運(yùn)行時支持的。strtok的源代碼在strtok.c中。從代碼可見,在多線程情況下,strtok的第一句有效代碼就是_ptiddata ptd = _getptd(),它通過這個來獲得當(dāng)前的ptd。可是我們并沒有通過_beginthread來創(chuàng)建ptd,那么一定是_getptd搗鬼了。打開tidtable.c,可以看到_getptd的實(shí)現(xiàn),果然,它先嘗試獲得當(dāng)前的ptd,如果不能,就重新創(chuàng)建一個,因此,后續(xù)的CRT調(diào)用就安全了。可是這塊ptd最終又是誰釋放的呢?打開dllcrt0.c,可以看到一個DllMain函數(shù)。在VC中,CRT既可以作為一個動態(tài)鏈接庫和主程序鏈接,也可以作為一個靜態(tài)庫和主程序鏈接,這個在Project Setting->Code Generations里面可以選。當(dāng)CRT作為DLL鏈接到主程序時,DllMain就是CRT DLL的入口。Windows的DllMain可以由四種原因調(diào)用:Process Attach/Process Detach/Thread Attach/Thread Detach,最后一個,也就是當(dāng)線程函數(shù)退出后但是線程還沒有銷毀前,會在這個線程的上下文中用Thread Detach調(diào)用DllMain,這里,CRT做了一個_freeptd(NULL),也就是說,如果有ptd,就free掉。所以說,恰巧沒有發(fā)生內(nèi)存泄漏是因?yàn)槟阌玫氖莿討B(tài)鏈接的CRT。
            于是我們得出了一個更精確的結(jié)論,如果我沒有使用那些會使用_getptd的CRT函數(shù),使用CreateThread就是安全的。

            V. 使用ptd的函數(shù)

            那么,究竟那些函數(shù)使用了_getptd呢?很多!在CRT目錄下搜索_getptd,你會發(fā)覺很多意想不到的函數(shù)都用到了它,除了strtok、rand這類需要保持狀態(tài)的,還有所有的字符串相關(guān)函數(shù),因?yàn)樗鼈円玫絧td中的locale信息;所有的mbcs函數(shù),因?yàn)樗鼈円玫絧td中的mbcs信息,...。

            VI. 測試代碼

            下面是一段測試代碼(leaker中用到了atoi,它需要ptd):

            #include
            #include
            #include
            #include

            volatile bool threadStarted = false;

            void leaker()
            {
            ??? std::cout << atoi( "0" ) << std::endl;
            }

            DWORD __stdcall CreateThreadFunc( LPVOID )
            {
            ??? leaker();
            ??? threadStarted = false;
            ??? return 0;
            }

            DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID )
            {
            ??? leaker();
            ??? threadStarted = false;
            ??? _endthread();
            ??? return 0;
            }

            void __cdecl beginThreadFunc( LPVOID )
            {
            ??? leaker();
            ??? threadStarted = false;
            }

            int main()
            {
            ??? for(;;)
            ??? {
            ??????? while( threadStarted )
            ??????????? Sleep( 5 );
            ??????? threadStarted = true;
            //????? _beginthread( beginThreadFunc, 0, 0 );//1
            ??????? CreateThread( NULL, 0, CreateThreadFunc, 0, 0, 0 );//2
            //????? CreateThread( NULL, 0, CreateThreadFuncWithEndThread, 0, 0, 0 );//3
            ??? }
            ??? return 0;
            }

            如果你用VC的多線程+靜態(tài)鏈接CRT選項(xiàng)去編譯這個程序,并且嘗試打開1、2、3之中的一行,你會發(fā)覺只有2打開的情況下,程序才會發(fā)生內(nèi)存泄漏(可以在Task Manager里面明顯的觀察到)。3之所以不會出現(xiàn)內(nèi)存泄漏是因?yàn)橹鲃诱{(diào)用了_endthread。

            VII. 總結(jié)

            如果你使用了DLL方式鏈接的CRT庫,或者你只是一次性創(chuàng)建少量的線程,那么你或許可以采取鴕鳥策略,忽視這個問題。上面一節(jié)代碼中第3種方法基于對CRT庫的了解,但是并不保證這是一個好的方法,因?yàn)槊恳粋€版本的VC的CRT可能都會有些改變。看來,除非你的頭腦清晰到可以記住這一切,或者你可以不厭其煩的每調(diào)用一個C函數(shù)都查一下CRT代碼,否則總是使用_beginthread(或者它的兄弟_beginthreadex)是一個不錯的選擇。

            [后記]
            網(wǎng)友condor指出本文的一個錯誤:在dllcrt0.c中,DllMain的Thread Detach所釋放的ptd,其實(shí)是dllcrt0.c的DllMain中的Thread Attach所創(chuàng)建的。也就是說,當(dāng)你用CRT DLL的時候,DllMain對線程做了一切初始化/清除工作。我查看源代碼,thread.c中的_threadstart函數(shù),在設(shè)置TLS之前做了檢查,這其實(shí)就是為了避免重復(fù)設(shè)置導(dǎo)致的內(nèi)存泄漏。

            posted on 2006-11-25 00:51 獨(dú)孤九劍 閱讀(5161) 評論(0)  編輯 收藏 引用 所屬分類: Win32Visual C++ 8.0
            伊人色综合久久| 狠狠精品干练久久久无码中文字幕| 国产福利电影一区二区三区久久老子无码午夜伦不 | 狠狠狠色丁香婷婷综合久久俺| 少妇熟女久久综合网色欲| 久久久国产精品| 久久黄视频| 精品乱码久久久久久夜夜嗨 | 久久综合九色综合精品| 国产∨亚洲V天堂无码久久久| 久久国产亚洲精品无码| 精品无码久久久久国产| 久久人人爽爽爽人久久久| 久久精品夜夜夜夜夜久久| 国产精品无码久久久久久| 99久久99久久| 久久精品成人一区二区三区| 99久久精品国产一区二区蜜芽 | 久久精品国产精品亚洲下载| 久久精品国产一区二区| 亚洲日韩欧美一区久久久久我| 青青草原综合久久大伊人| 亚洲人成网亚洲欧洲无码久久| 久久这里只有精品18| 国产欧美一区二区久久| 国产精品美女久久久久av爽| 色综合久久中文字幕综合网| 久久国产免费直播| 国产69精品久久久久777| 久久se这里只有精品| 久久久久久久精品成人热色戒| 久久九九精品99国产精品| 一级做a爱片久久毛片| 欧美久久一级内射wwwwww.| 久久久久国产精品人妻| 久久国产免费观看精品| 欧美粉嫩小泬久久久久久久 | 国产精品内射久久久久欢欢| 武侠古典久久婷婷狼人伊人| 久久亚洲精精品中文字幕| 精品国产91久久久久久久a|