天天看點

《程式員的自我修養》讀書筆記

1、windows API中的 堆管理器     HeapCreate:建立一個堆     HeapAlloc:在一個堆裡配置設定記憶體     HeapFree:釋放已經配置設定的記憶體     HeapDestroy:摧毀一個堆     但是實際上,系統申請堆空間是調用VirtualAlloc()(類似于linux下的brk()和mmap()系統調用),在windows下一個程序通常通過malloc()可以申請1.5G的堆空間,而在linux下可以申請到2.9G大小的堆空間。

2、windows下c/c++程式 入口函數 初始化流程( mainCRTStartup )     (1)初始化和OS版本有關的全局變量     (2)初始化堆(直接調用HeapCreate建立堆)     (3)初始化I/O:             建立打開檔案表;             如果能夠繼承自父程序,那麼從父程序擷取繼承的句柄;             初始化标準輸入輸出     (4)擷取指令行參數和環境變量      (5)初始化C庫的一些資料         (6)調用main并記錄傳回值     (7)檢查錯誤并将main函數的傳回值傳回     即main函數不是c程式的入口,linux下的 __libc_start_main和windows下的mainCRTStartup才是真正的入口函數,他們完成程序運作的初始化工作,然後執行main函數,最後進行清理工作。

3、在c++裡傳回一個對象的時候,對象要 經過2次拷貝構造函數的調用才能完成傳回對象的傳遞。一次拷貝到棧上的臨時對象裡,另外一次把臨時對象拷貝到存儲傳回值的對象裡。是以, 在c++程式中應盡量避免傳回對象或struct。     可以通過傳回值優化(Return Value Optimization)在某些場合減少一次對象拷貝過程。

4、一般來講,應用程式使用的記憶體空間有如下“預設”區域:     (1)棧:用于維護函數調用上下文,通常有數兆位元組的大小;     (2)堆:用來容納應用程式動态配置設定的記憶體區域,當程式用malloc或new配置設定記憶體時,得到的記憶體來自堆;     (3)可執行檔案映像:存儲可執行檔案在記憶體裡的映像;     (4)保留區:對記憶體中受保護而禁止通路的記憶體區域總稱;     其中,在linux下動态連結庫位于堆和棧之間(從0x40000000開始),而程式運作時出現“段錯誤”或者“非法操作,該記憶體位址不能read/write”的錯誤資訊,通常是由于沒有初始化棧上的指針造成的,或者通路了初始化不合理的指針。

5、 setjmp與 longjmp非局部跳轉 #include <setjmp.h> #include <stdio.h>

jmp_buf b; void f() { longjmp(b, 1); }

int main(int argc, char* argv[]) { if(setjmp(b)) printf("World!"); else { printf("Hello "); f(); } return 0; }

這段代碼輸出為:“Hello World!”,setjmp(b)傳回0,是以首先列印出“Hello”,但是longjmp(b, 1)讓程式傳回(程式執行流回流)到setjmp傳回的時刻,并傳回longjmp指定的第二個參數,即1,是以程式列印出“World!”。 longjmp能夠讓程式“時光倒流”到setjmp傳回的時刻。

6、DLL顯示運作時連結 與ELF類似,DLL也支援運作時連結,即運作時加載,windows提供以下3個API:     (1) LoadLibrary(LoadLibraryEx),用來裝載一個DLL到程序位址空間,功能類似于dlopen;     (2) GetProcAddress,用來查找某個符号的位址,與dlsym類似;     (3) FreeLibrary,用來解除安裝某個已經加載的子產品,與dlclose類似;     DLL的代碼不是位址無關的

7、線程局部(私有)存儲     線程擁有自己的私有存儲空間包括:棧、線程局部存儲(Thread Locale Storage,TLS)、寄存器;     可以通過 __thread(linux)或者 __declspec(thread)(windows)來定義一個線程私有的全局變量,例如: __thread int number; 這樣每個線程都會擁有一個這個變量的副本,任何線程對該變量的修改都不會影響其他線程中該變量的副本。在具體實作時,将TLS變量放在PE檔案中“.tls”段中;他是從堆中配置設定的一塊空間,最多可以擁有 1088(1024+64)個顯式TLS變量。windows提供了如下4個API進行顯式TLS操作:TlsAlloc、TlsGetValue、TlsSetValue、TlsFree,linux下則為:pthread_key_create、pthread_getspecific、pthread_setspecific、pthread_key_delete。     在windows下進行多線程開發時,盡量用 _beginthread()/_beginthreadex()/_endthread()/_endthreadex()這組函數來建立線程,在MFC中則用AfxBeginThread()和AfxEndThread(),避免出現CreateThread()和ExitThread()函數會出現記憶體洩露的情況。     由于CreateThread API中可能會申請一塊記憶體(_tiddata結構,儲存線程的顯式TLS數組),而ExitThread() API不會釋放這塊記憶體,是以盡量避免調用這組API 建立線程,特别是在靜态連結的情況下,動态連結不存在問題。 在動态連結的CRT中,DllMain會釋放掉_tiddata,靜态連結則沒有釋放,出現記憶體洩露。 測試代碼如下: #include<windows.h> #include<process.h>

void thread(void *a) { char* r = strtok("aaa", "b"); ExitThread(0); }

int main(int argc, char* argv[]) { while(1) { CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread, 0, 0, 0); Sleep(5); } return 0; } 動态連結的CRT(/MD, /MDd)不會出現問題,而靜态連結(/MT, /MTd),記憶體使用量會不斷的上升。

8、C++全局構造與析構 glibc實際上是調用的 __do_global_ctors_aux()函數進行初始化全局對象,該函數位于GCC提供的 crtbegin.o目标檔案中,在 __CTOR_LIST__裡面存放了所有全局對象的構造函數指針,依次執行這些構造函數即可完成全局對象的構造;在每個對象的構造函數中,還注冊了一個析構函數(通過回調的方式,例如: atexit(__tcf_1);),最後析構函數會以構造函數相反的順序執行,進行全局對象的析構。 編譯器在連結目标檔案時将編譯時為每個編譯單元生成的特殊函數(GLOBAL__I_Hw全局構造函數與注冊析構函數)合并在一起,即每個目标檔案的.ctors段會被合并為一個 .ctors段,拼接起來的.ctors段就成為了一個函數指針數組,每一個元素都指向一個目标檔案的全局構造函數。 要想自己定義的函數在main函數之前執行,則可以通過手動将函數指針添加到.ctors段裡即可:      ctor_t __attribute__((section (".ctors"))) my_init_p = &my_init;  //my_init為自定義的函數     或者     void my_init(void) __attribute__((constructor))     全局析構函數是通過 __cxa_exit()在 exit()函數中注冊程序退出時的回調函數 MSVCRT 的入口函數調用_initterm()函數進行全局構造,通過指針__xc_a和__xc_z之間存儲的函數指針,依次調用進行構造。所有的全局構造函數存放在 .CRT(每一個編譯單元都會生成.CRT$XCU的組, 其中U表示user,編譯單元在這個組中加入自己的全局初始化函數)段中,連結合并後按字母序依次排列;自己可以通過如下方式手動添加初始化代碼,使其在main之前執行: #include<iostream>

#define SECNAME ".CRT$XCG" #pragma section(SECNAME, long, read); void foo() { std::cout<<"hello, world"<<std::endl; }

typedef void (__cdecl *_PVFV)(); __declspec(allocate(SECNAME)) _PVFV dummy[] = { foo };

int main() { return 0; }     MSVC CRT的全局析構通過 atexit()函數實作