[TOC]
# 逐過程調試(F10)和逐語句調試(F11)

當需要跳出函數時,可以點擊“跳出”按鈕,或者按`Shift+F11`鍵,就會返回剛才的代碼。
`逐過程(F10)`和`逐語句(F11)`都可以用來進行單步調試,但是它們有所區別:
* `逐過程(F10)`在遇到函數時,會把函數從整體上看做一條語句,不會進入函數內部;
* `逐語句(F11)`在遇到函數時,認為函數由多條語句構成,會進入函數內部。
`逐語句(F10)`不僅可以進入庫函數的內部,還可以進入自定義函數內部。在實際的調試過程中,兩者結合可以發揮更大的威力
# 即時窗口
“即時窗口”是VS提供的一項非常強大的功能,在調試模式下,我們可以在即時窗口中輸入C語言代碼并立即運行,如下圖所示:

在即時窗口中可以使用代碼中的變量,可以輸出變量或表達式的值(無需使用printf()函數),也可以修改變量的值。
即時窗口本質上是一個命令解釋器,它負責解釋我們輸入的代碼,再由VS中的對應模塊執行,最后將輸出結果呈現到即時窗口。
需要注意的是,在即時窗口中不能定義新的變量,因為程序運行時 Windows 已經為它分配好了只夠剛好使用的內存,定義變量是需要額外分配內存的,所以調試器不允許在程序運行的過程中定義變量,因為這可能會導致不可預知的后果
**調用函數**
在即時窗口中除了可以使用代碼中的變量,也可以調用代碼中的函數。將下面的代碼復制到源文件中
~~~
int plus(int x, int y){
return x + y;
}
int main(){
return 0;
}
~~~
在第6行設置斷點,并在即時窗口中輸入`plus(5, 6)`,如下圖所示:

# 查看修改運行時內存
首先我們通過內存窗口查看變量的值,我們啟動 Visual Studio,創建一個工程,輸入如下代碼:
~~~
#include <stdio.h>
int main()
{
int testNumber = 5678;
printf("testNumber 的內存地址為 0x00%x \n", &testNumber);
//輸出內存地址
//TODO:在這里插入斷點
return 0;
}
~~~
我們在第七行設置好斷點,然后按 F5 啟動調試,等待觸發斷點。觸發斷點后,我們發現,IDE中并沒有顯示內存窗口(默認設置下),這時,我們點擊菜單 -> 調試(D) -> 窗口 (W) -> 內存 (M) -> 內存1(1),就可以調出內存窗口了,如圖:
我們看到,內存窗口里面顯示著一大堆亂七八糟的數據,這里面的這些就是我們內存中的數據啦,我們可以通過變量 testNumber 的內存地址跳轉到器對應的內存空間,我們看到 testNumber 的地址為 0x0018f830 (在不同的進程是不一樣的),我們把這個地址輸入到我們的內存窗口的地址欄。如圖:

我們看到,盡管我們已經輸入了正確地地址,但是我們還是沒有看到正確的數據顯示,其實原因非常簡單,我們來回顧一下 C 語言的一些入門知識:我們知道,在我們的源代碼中,我們的 testNumber 變量被定義為 int 整形,我們再想想 int 整形在內存中占幾個字節?沒錯,是4個字節。所以我們應該以四字節的形式格式化這些內存數據,這是我們在內存窗口中單擊我們的鼠標右鍵,在彈出的菜單中選擇“4字節整數(4)”,然后就能正確地顯示相關的數據了,如圖:

沒錯,查看內存就是這么的簡單。接下來我們就來查看與修改浮點數的內存數據,我們看下面這段代碼:
~~~
#include <stdio.h>
int main()
{
double pi = 3.141592653589;
printf("pi 的內存地址為 %x \n", &pi);
//輸出內存地址
//TODO:在這里插入斷點
return 0;
}
~~~
同樣的,我們在第7行設置斷點,按F5啟動調試,等待斷點被觸發:

這時我們看到的內存地址是這樣的,與我們在內存窗口看到的不同,我們需要將其補齊,在我們現階段編寫的小程序中,顯示的內存地址基本上都是六位的,我們在前面加上 “0x00”,將其補到八位(內存窗口上的地址欄里有幾位就補到幾位)。然后我們將其輸入到內存窗口上的地址欄
我們發現,現在顯示的數據依然是錯誤的,因為查看器現在還是在使用我們之前設置的 4位整形格式 格式化我們的內存數據呢,我們知道,我們的 double 屬于64位浮點數,所以我們在查看窗口點擊鼠標右鍵,在彈出的菜單中選擇“64位浮點(6)”,然后我們就能看到它正確地輸出數據了

我們注意到,在我們設置的變量pi的值的基礎上,內存窗口里顯示的數據多了幾個0,這些0所代表的就是 double 型數據的最高精度。接下來我們嘗試在內存窗口修改變量 pi 的值,為其補齊精度。現在我們用鼠標右鍵點擊我們的pi的內存數據,在彈出的菜單中選擇編輯值(E),在顯示的輸入框中我們輸入 3.1415926535897931,按回車即可保存。我們看看效果

怎么樣,內存的查看與修改是不是很簡單呢?其實我們只要記住下面的幾個對應關系,常用的數值數據類型的內存查看與修改都不在話下

# 條件斷點
大家有沒有碰到這樣的情況,在一個循環體中設置斷點,假設有一千次循環,我們想在第五百次循環中設置斷點,這該怎么辦?反正設置斷點不斷按 F5 繼續運行四百九十九次是不可能的。那該怎么辦呢?其實我們的斷點是可以設置各種各樣的條件的,對于上述情況,我們可以對斷點的命中次數做一個限制。
我們首先在 Visual Studio 中創建一個工程,并且輸入如下代碼:
~~~
#include <stdio.h>
int main(){
for ( int i=1 ; i <= 1000 ; i++ ) {
//TODO:插入計次斷點
printf("我真行!\n");
}
}
~~~
首先我們用鼠標右鍵單擊第4行的斷點圖標,在彈出的菜單中選擇 命中次數(H) ,接下來會彈出如下圖的一個對話框,我們在中間的選擇框中選擇 “中斷,條件是命中次數等于”,我們在右邊的編輯框輸入 500。

我們點擊確定,斷點就設置到位了,接下來我們按 F5 運行調試。

我們看到,在輸出四百九十九行“我真行!”后,程序進入了中斷狀態,這是我們注意到自動窗口中的變量 i 的值為 500,接下來我們把這個 i 的值改為 1,點擊 繼續(C) 繼續程序的運行,這樣程序就再輸出了一千行“我真行!”,然后退出。沒錯,命中次數限制的使用就是這么簡單。
我們再次用鼠標右鍵單擊第4行的斷點圖標,在彈出的菜單中選擇 命中次數(H) ,大家如果有興趣的話,可以試試中間的選擇框中其他的條件選項,使用方法基本一致,這里不再贅述。
接下來我們來了解一下斷點觸發條件的使用,在 Visual Studio 的調試器中,我們可以對斷點設置斷點觸發條件,這個條件可以引用我們程序中的變量,比如我們程序中有兩個變量 a、b ,我們的命中條件可以是 a == b 、 a >= b 、 a != b 甚至是 (a - b)\*(a\*2 - b) > 0 這樣的復雜條件。
# assert斷言函數
,這個函數在 assert.h 頭文件中被定義,在微軟的 cl 編譯器中它的原型是這樣的:
~~~
#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
~~~
我們看到 assert 在 cl 編譯器中被包裝成了一個宏,實際調用的函數是 \_wassert ,不過在一些編譯器中,assert 就是一個函數。為了避免這些編譯器差異帶來的麻煩,我們就統一將 assert 當成一個函數使用。
我們來了解一下 assert 函數的用法和運行機制,assert 函數的用法很簡單,我們只要傳入一個表達式即可,它會計算我們傳入的表達式的結果,如果為真,則不會有任何操作,但是如果我們傳入的表達式的計算結果為假,它就會像? stderr (標準錯誤輸出)打印一條錯誤信息,然后調用 abort 函數直接終止程序的運行。
現在我們在 Visual Studio 中創建一個工程,輸入下面這段非常簡單的代碼:
~~~
#include <stdio.h>
#include <assert.h>
int main()
{
printf("assert 函數測試:");
assert(true); //表達式為真
assert(1 >= 2); //表達式為假
return 0;
}
~~~
我們看到,我們的輸出窗口打印出了斷言失敗的信息,并且 Visual Studio 彈出了一個對話框詢問我們是否繼續執行。但是如果我們不綁定調試器,構建發布版程序,按 Ctrl + F5 直接運行呢?是的,這個 assert 語句就無效了,原因其實很簡單,我們看看 assert.h 頭文件中的這部分代碼:
~~~
#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else /* NDEBUG */
~~~
我們看到,只要我們定義了 NDEBUG 宏,assert 就會失效,而 Visual Studio 的默認的發布版程序編譯參數中定義了 NDEBUG 宏,所以我們不用額外定義,但是在其他編譯器中,我們在發布程序的時候就必須在包含 assert.h 頭文件前定義 NDEBUG 宏,避免 assert 生效,否則總是讓用戶看到“程序已經停止運行,正在尋找解決方案 . . .”的 Windows 系統對話框可就不妙了。
下面我們來了解一下 assert 的常用情境以及一些注意事項。舉個C語言文件操作的例子:
~~~
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
int main(void)
{
FILE *fpoint;
fpoint = fopen("存在的文件.txt", "w");
//以可讀寫的方式打開一個文件
//如果文件不存在就自動創建一個同名文件
assert(fpoint);
//(第一次斷言)所以這里斷言成功
fclose(fpoint);
fpoint = fopen("不存在的文件.txt", "r");
//以只讀的方式打開一個文件,如果不存在就打開文件失敗
assert(fpoint);
//(第二次斷言)所以斷言失敗
printf("文件打開成功");
//程序永遠都執行不到這里來
fclose(fpoint);
return 0;
}
~~~
閱讀代碼,我們可以知道,如果我們錯誤的設置了讀寫權限,或者沒有考慮 Windows 7(或更高版本) 的 UAC(用戶賬戶權限控制) 問題的話,我們的程序出現的問題就很容易在我們的測試中暴露出來。事實上,上面的代碼也不是一定能通過第一次斷言的,我們把構建生成的(Debug 模式的)可執行文件復制到我們的系統盤的 Program Files 文件夾再執行,那么 Win7 及以上的操作系統的UAC都會導致程序無法通過第一次斷言的。所以在這種情況下我們就需要在必要的情況申請管理員權限避免程序BUG的發生。
在我們的實際使用過程中,我們需要注意一些使用 assert 的問題。首先我們看看下面的這個斷言語句:
~~~
//...
assert( c1 /*條件1*/ && c2 /*條件2*/ );
//...
~~~
我們思考一下:如果我們的程序在這里斷言失敗了,我們如何知道是 c1 斷言失敗還是 c2 斷言失敗呢,答案是:沒有辦法。在這里我們應該遵循使用 assert 函數的第一個原則:每次斷言只能檢驗一個條件,所以上面的代碼應該改成這樣:
~~~
//...
assert(c1 /*條件1*/);
assert(c2 /*條件2*/);
//...
~~~
切換一下編譯模式到發布模式:

我們看到了一個完全不相同的運行結果,這是為什么呢?其實原因很簡單,我們注意這段代碼:
~~~
assert(i++ <= 100);
~~~
我們的條件表達式為 i++ <= 100,這個表達式會更改我們的運行環境(變量i的值),在發布版程序中,所有的 assert 語句都會失效,那么這條語句也就被忽略了,但是我們可以把它改為 i++ ; assert(i <= 100); ,這樣程序就能正常運行了。所以請記住:不要使用會改變環境的語句作為斷言函數的參數,這可能導致實際運行中出現問題。
最后,我們再來探討一下,什么時候應該用 assert 語句?一個健壯的程序,都會有30%~50%的錯誤處理代碼,幾乎用不上 assert 斷言函數,我們應該將 assert 用到那些極少發生的問題下,比如Object\* pObject = new Object,返回空指針,這一般都是指針內存分配出錯導致的,不是我們可以控制的。這時即使你使用了容錯語句,后面的代碼也不一定能夠正常運行,所以我們也就只能停止運行報錯了。
# OutputDebugString調試信息輸出
Windows 操作系統提供的函數 —— OutputDebugString,這個函數非常常用,他可以向調試輸出窗口輸出信息(無需設置斷點,執行就會輸出調試信息),并且一般只在綁定了調試器的情況下才會生效,否則會被 Windows 直接忽略。接下來我們了解一下這個函數的使用方法。
首先,這個函數在 windows.h 中被定義,所以我們需要包含 windows.h 這個頭文件才能使用 OutputDebugString 函數。這個函數的使用方式非常的簡單,它只有簡單的一個參數——我們要輸出的調試信息。但是有一點值得注意:準確來說 OutputDebugString 并不是一個函數,他是一個宏。在高版本的 Visual Studio 中,因為編譯的時候 Visual Studio 默認定義了 UNICODE 宏,所以我們查找 OutputDebugString 的定義會看到如下代碼:
~~~
#ifdef UNICODE
#define OutputDebugString OutputDebugStringW
#else
#define OutputDebugString OutputDebugStringA
#endif // !UNICODE
~~~
我們可以從代碼高亮上看到,OutputDebugString 實際上等價于 OutputDebugStringW,這就意味著我們必須傳入寬字符串(事實上只要定義了 UNICODE ,調用所有 Windows 提供的函數都需要使用寬字符),或者使用 TEXT 或 \_T 宏,并且這是最好的方法,這個宏會自動識別編譯器是否處于默認寬字符的狀態并對傳入字符串做些處理,使用這個宏可以加強代碼對不同編譯器不同編譯參數的兼容性。下面我們就來看一段示例代碼
~~~
#include <windows.h>
#include <tchar.h>
int main() {
OutputDebugString(TEXT("你好"));
OutputDebugString(_T("hello world"));
//也可以:OutputDebugStringA("大家好才是真的好。");
//也可以:OutputDebugStringW(L"大家好才是真的好。");
//使用自動字符宏 TEXT 或者 _T 可以自動判斷是否使用寬字符
getchar();
return 0;
}
~~~

但在 Windows 下,我們一般使用 \\r\\n 作為完整的換行符。
直接使用這個調試信息輸出函數有個弊端,那就是它不能直接輸出含參數的字符串。但是我們可以通過 sprintf / wsprintf 等緩沖區寫入函數來間接實現輸出含參數字符串的功能。下面是一段示例代碼:
~~~
#include <stdio.h>
#include <windows.h>
int main(){
//注意!這段代碼我們指定使用ANSI字符!
char szBuffer[200];
int number = 100;
sprintf_s(szBuffer, "變量 number 的值是 %d \r\n", number); //寫入緩沖區,注意不要溢出
OutputDebugStringA(szBuffer);
sprintf_s(szBuffer, "變量 number 的地址是 %x \r\n", &number);
OutputDebugStringA(szBuffer);
//我門指定使用 ANSI 版本的 OutputDebugString 函數
return 0;
}
~~~
我們可以自己寫一個前端函數,然后保存到頭文件中(編譯生成 dll 也可以,有興趣的同學可以試試)。為了方便,我們已經編寫好了這么一套函數。代碼如下:
~~~
#include <stdio.h>
#include <windows.h>
#ifndef _DEBUG_INFO_HEADER_
//防止頭文件被重復載入出錯
#define _DEBUG_INFO_HEADER_
#if (defined UNICODE)||(defined _UNICODE)
#define DebugInfo DebugInfoW
#else
#define DebugInfo DebugInfoA
#endif
// 函數: DebugInfoA(char*, int, ...)
//
// 目的: 以窄字符的形式輸出調試信息
//
// char* str - 格式化 ANSI 字符串
// ... - 任意不定長參數
//
void DebugInfoA(char* str, ...){
char szBuffer[500]; //注意不要讓緩沖區溢出!
va_list Argv;
va_start(Argv, str);
_vsnprintf_s(szBuffer, 500, str, Argv);
va_end(Argv);
OutputDebugStringA(szBuffer);
}
// 函數: DebugInfoW(char*, int, ...)
//
// 目的: 以寬字符的形式輸出調試信息
//
// char* str - 格式化 UNICODE 字符串
// ... - 任意不定長參數
//
void DebugInfoW(wchar_t* str, ...){
wchar_t szBuffer[1000];
va_list Argv;
va_start(Argv, str);
_vsnwprintf_s(szBuffer, 500, str, Argv);
va_end(Argv);
OutputDebugStringW(szBuffer);
}
#endif
~~~
的這段代碼會自動識別編譯器是否默認使用了寬字符并且使用對應版本的輸出函數,其中注釋為 Visual Studio 的智能提示信息,我們把上面的代碼保存到 debuginfo.h 并添加到當前工程中,就可以直接通過如下代碼調用:
~~~
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include "debuginfo.h"
int main(){
int num;
//這里我們使用微軟提供的 xxxx_s 安全函數
printf_s("請輸入數值:\n");
scanf_s("%d", &num);
DebugInfo(TEXT("用戶輸入的數是 %d\n"), num);
return 0;
}
~~~
但是請注意不要讓緩沖區溢出,否則會造成不可估計的后果。
我們除了在調試器中可以看到調試字符串的輸出,我們還可以借助 Sysinternals 軟件公司研發的一個相當高級的工具 —— DebugView 調試信息捕捉工具,這個工具可以在隨時隨地捕捉 OutputDebugString 調試字符串的輸出(包括發布模式構建的程序),可以說這是個神器,大家可以在微軟 MSDN 庫上搜索下載。接下來我們運行 DebugView 調試信息捕捉工具。
這個調試信息查看工具顯示了正確的調試信息。注意!如果掛載了 Visual Studio 的調試器,這個工具就會失效。
使用這個工具可以捕獲發布版程序的輸出,我們一般可以用來跟蹤測試產品的運行狀態。以趁早發現我們程序中存在的問題,避免發布后出現的容易被檢測到的BUG。
# Release和Debug模式
首先我們再了解一下Visual Studio 中,Release構建模式和Debug 構建模式的區別。我們在

可以切換構建模式。Release構建模式下構建的程序為發行版,而Debug構建模式下構建的程序為調試版。在 Visual Studio 中調試模式還會定義兩個宏 \_DEBUG 和 DEBUG,后文我們將介紹它們的一些妙用。在 Visual Studio 中,如果我們要更改編譯參數的話,可以點擊菜單 -> 項目(P) -> 屬性(P),我們在彈出的頁面左側選擇配置屬性即可對編譯參數進行修改。
接下來,我們來了解一下調試標記。不知道大家有沒有遇到這樣的情況,我們需要在調試的時候額外運行一段代碼,但是實際發布的時候卻不需要這段代碼呢。那該怎么辦,絕大多數數的初學者會選擇使用注釋,即在發布的時候將無用的測試代碼注釋掉。但是這樣很麻煩,下面我們就為大家介紹一種全新的方法——使用調試標記。事實上這種方法我們在前面使用過,但是沒有詳細講解。
這種方法借助了預處理指令,方法很簡單,我們首先定義一個宏作為處于調試狀態的標記,后面的代碼我們用 #ifdef 和 #endif 預處理指令檢測宏是否被定義,然后由編譯器判斷是否編譯其中的代碼。這么做的好處就是可以減少發布程序的體積,另一方面可以提高發布程序的運行效率。下面是一段示范代碼:
~~~
#include <stdio.h>
#define _DEBUGNOW
int main(){
#ifdef _DEBUGNOW
printf("正在為調試做準備...");
#endif // _DEBUGNOW
printf("程序正在運行...");
return 0;
}
~~~
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介