編寫高效 C 代碼不僅依賴編譯器優化,更需要程序員理解底層硬件特性與編譯器行為。本文系統匯總整型運算、除法優化、取模替換、數組索引、全局/局部變量、指針別名、循環優化、函數設計、浮點運算、查找表等 20+ 實戰優化技巧,附代碼對比與原理解析,助你寫出又快又省的 C 代碼。
// 非負計數器優先用 unsigned int,配合 register 提示編譯器用寄存器
register unsigned int counter = 0;
for (; counter < LIMIT; counter++) { ... }
register 只是建議,編譯器可能忽略,但無副作用。32 位除法需 20-140 時鐘週期(ARM: 20+4.3N),極其昂貴。
(a/b) > c → a > c*b(已知 b>0 且不溢出)。a/b 和 a%b 為單指令。// 慢:取模指令
uint modulo_func1(uint count) { return (++count % 60); }
// 快:分支預測友好,適用 count 在 0-59 範圍
uint modulo_func2(uint count) { if (++count >= 60) count = 0; return count; }
// 通用快:任意範圍
uint modulo_func3(uint count) { if (++count >= 60) count %= 60; return count; }
// 慢:分支預測失敗開銷
switch (queue) { case 0: letter='W'; break; case 1: letter='S'; break; ... }
// 快:直接索引,O(1) 無分支
static char *classes = "WSU";
letter = classes[queue];
// 慢:每次訪問全局變量需內存讀寫
int errs;
void test1() { errs += f(); errs += g(); }
// 快:局部變量可分配寄存器
void test2() {
int localerrs = errs; // 載入一次
localerrs += f();
localerrs += g();
errs = localerrs; // 存回一次
}
// 慢:編譯器不知 *data 是否被 anyfunc 修改,每次循環重讀內存
void func1(int *data) {
for (i=0; i<10; i++) anyfunc(*data, i);
}
// 快:局部緩存,消除別名疑慮
void func1(int *data) {
int localdata = *data;
for (i=0; i<10; i++) anyfunc(localdata, i);
}
// 慢:每次 p->pos->x 都要重新載入 p->pos
void InitPos1(Object *p) {
p->pos->x = 0; p->pos->y = 0; p->pos->z = 0;
}
// 快:緩存 p->pos 到局部指針
void InitPos2(Object *p) {
Point3 *pos = p->pos;
pos->x = 0; pos->y = 0; pos->z = 0;
}
if (a>0 && b>0 && c<0 && d<0) 便於編譯器條件化執行。(x>=min && x,尤其 min=0 時極快。 x<0, x==0, x!=0 可複用數據處理指令設置的標誌位,省去 CMP 指令。if (a>10 && b=4):先判斷最可能為 false 的條件。return table + stride*index; 比 switch 更小更快。| 技巧 | 代碼模式 | 原理 |
|---|---|---|
| 倒數到零 | for(i=n; i; i--) | 測試 i!=0 比 i |
| 循環融合 | 合併兩個相同範圍循環 | 減少循環開銷,提高指令緩存命中 |
| 函數內循環 | 將循環移入被調用函數 | 消除函數調用開銷 |
| 循環展開 | 手動/編譯器展開小循環 | 減少分支、增加指令級並行 |
| 提前退出 | 找到目標即 break | 避免無效遍歷 |
__inline 消除調用開銷,適合小熱點函數。// 慢:switch 240 字節
char *Condition_String1(int c) { switch(c) { case 0: return "EQ"; ... } }
// 快:查找表 72 字節
char *Condition_String2(int c) {
if ((unsigned)c >= 15) return 0;
return "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" + 3*c;
}
x/3.0 → x*(1.0/3.0),倒數編譯期計算。(a+b)+c ≠ a+(b+c)。static 限制作用域。#define SQR(x) ((x)*(x)) 消除調用。C 語言優化的核心是理解硬件與編譯器:
警示:過度優化傷害可讀性與可維護性。先寫清晰正確代碼,再用 Profiler 定位熱點,針對性優化。正如 Knuth 所言:「過早優化是萬惡之源。」