關於CreateThread()的幾點疑惑

一、爲什麼要特意去CloseHandle()?

線程中止運行後,線程對象仍然在系統中,必須通過CloseHandle函數來關閉該線程對象。CloseHandle函數的原型是:

BOOL CloseHandle( HANDLE hObject );//HANDLE hObject 對象句柄

CloseHandle可以關閉多種類型的對象,比如文件對象等,這裏使用這個函數來關閉線程對象。調用時,hObject爲待關閉的線程對象的句柄。
如果不使用這種函數去關閉線程句柄則會引起內存的泄漏,爲什麼會這樣呢?因爲當線程的函數用到了C的標準庫的時候,很容易導致衝突,所以在創建VC的工程時,系統提示是用單線程還是用多線程的庫,因爲在C的內部有很多的全局變量。例如,出錯號、文件句柄等全局變量。因爲在C的庫中有全局變量,這樣用C的庫時,如果程序中使用了標準的C程序庫時,就很容易導致運行不正常,會引起很多的衝突。所以,微軟和Borland都對C的庫進行了一些改進。但是這個改進的一個條件就是,如果一個線程已經開始創建了,就應該創建一個結構來包含這些全局變量,接着把這些全局變量放入線程的上下文中和這個線程相關起來。這樣,全局變量就會依賴於這個線程,不會引起衝突。
這樣做就會有一個問題,什麼時候這個線程開始創建呢?標準的Windows的API是不知道的,因爲它是靜態的庫。這些庫都是放在VC的LIB的目錄內的,而線程函數是操作系統的函數。所以,VC和BC在創建線程時,都會用_beginThread來創建線程,再用_endThread來結束線程。這樣,它們在創建線程的時候,就會知道什麼時候創建了線程,並把全局變量放入某一結構中,讓它和線程能關聯起來。這樣就不會發生衝突了。
很顯然,要完成這個功能,首先需要分配結構表把全局變量包含起來。這個過程是在_beginThread時做的,而釋放則是在_endTread內完成。所以,當用_beginThread來創建,而用CloseHandle來關閉線程時,這時複製的全局結構就不會被釋放了,這就有了內存的泄漏。

二、線程函數被聲明爲DWORD WINAPI 函數名(LPVOID lpParamter),其中的WINAPI是什麼意思?

typedef unsigned long       DWORD;
#define WINAPI __stdcall //__stdcall是一種調用約定,代表函數參數從右到左入棧

1、函數調用約定:
函數調用約定,是指當一個函數被調用時,函數的參數會被傳遞給被調用的函數和返回值會被返回給調用函數。函數的調用約定就是描述參數是怎麼傳遞和由誰平衡堆棧(清理堆棧)的,當然還有返回值。

2、調用約定的幾種類型:
__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal

3、參數傳遞(入棧)順序
1.從右到左依次入棧:__stdcall,__cdecl,__thiscall,__fastcall
2.從左到右依次入棧:__pascal

4、調用堆棧清理
1.調用者清除棧。
2.被調用函數返回後清除棧。

5、幾種調用約定的簡單介紹
__cdecl
1、參數是從右向左傳遞的,也是放在堆棧中。
2、堆棧平衡是由調用函數來執行的。
3、函數的前面會加一個前綴_(_sumExample)
這裏我們應該想想爲什麼不在被調函數內進行堆棧平衡呢?在這裏我們應該要考慮類似於像scanf和printf這樣的函數,這裏我們應該明白這兩個函數的參數都是可變的,如果參數不固定的話,在被調用函數內就無法知道參數究竟使用了多少個字節,所以爲了實現可變參數,我們必須要在被調函數執行之後我們才知道參數究竟用了多少字節,所以我們在調用者來進行堆棧平衡操作。

__stdcall
Win32 API函數絕大部分都是採用__stdcall調用約定的。WINAPI其實也只是__stacall的一個別名而已。

#define WINAPI __stdcall

與上面一樣,我們在函數的面前用__stdcall作爲修飾符。此時函數將會採用__stdcall調用約定
int __stdcall sumExample (int a, int b);
__stdcall調用約定的主要特徵是:
1、參數是從右往左傳遞的,也是放在堆棧中。
2、函數的堆棧平衡操作是由被調用函數執行的。
3、在函數名的前面用下劃線修飾,在函數名的後面由@來修飾並加上棧需要的字節數的空間(_sumExample@8)。
因爲棧的清理(堆棧平衡操作)是由被調用函數執行的。所以使用__stdcall調用約定生成的可執行文件要比__cdecl的要小,因爲在每次的函數調用都要產生堆棧清理的代碼。函數具有可變參數像我wsprintf這個函數,與前面的prinf一樣,都必須使用__cdecl調用約定,因爲只有調用者才知道參數的數量在每一次的函數調用,因此也只有調用者才能夠執行堆棧清理操作。

__fastcall
__fastcall見名知其意,其特點就是快。__fastcall函數調用約定表明了參數應該放在寄存器中,而不是在棧中,VC編譯器採用調用約定傳遞參數時,最左邊的兩個不大於4個字節(DWORD)的參數分別放在ecx和edx寄存器。當寄存器用完的時候,其餘參數仍然從右到左的順序壓入堆棧。像浮點值、遠指針和__int64類型總是通過堆棧來傳遞的。

三、線程函數是否可以聲明爲void*型?

線程函數可以聲明爲void*返回類型,只需要在創建線程時函數入口lpStartAddress要這樣通過LPTHREAD_START_ROUTINE轉換如: (LPTHREAD_START_ROUTINE)Fun。

typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) ( [in] LPVOID lpThreadParameter);

LPTHREAD_START_ROUTINE 指向的函數是回調函數,並且必須由承載應用程序的編寫器實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章