標籤:des style blog http color 使用
建議和規則
建議:
用內嵌函式或靜態函數代替與函數相似的宏
在宏參數名兩邊加上括弧
宏其他清單應該加上括弧
應該使用typedef定義編碼類別型
不要複用標準標頭檔名
理解串連標記或執行字串化時的宏替換
把標頭檔放在包含防護條件中
避免使用連續的問號
保證標頭檔名唯一
不要用不安全的函數替換安全函數
在一個do-while迴圈中封裝多條語句的宏
規則:
本文地址:http://www.cnblogs.com/archimedes/p/c-security-pretreatment-.html,轉載請註明源地址。
用內嵌函式或靜態函數代替與函數相似的宏
宏是危險的,用法與真正的函數相似,但是具有不同的語義。C99在C中增加了內嵌函式,當內嵌函式和宏可以互換使用時,應該優先選擇內嵌函式,內聯替換並不是文本替換,也沒有建立函數,決定一個函數是否為內嵌函式是一個底層的最佳化細節,編譯器應該不依賴程式換做出這個決定,是否使用內嵌函式取決於目標編譯器對它們的支援,它們對系統效能特徵所產生的影響以及可移植性問題,靜態函數常常具有與內嵌函式相同的優點。
下面的例子中,當傳遞給CUBE宏的參數是一個具有副作用的運算式時,這個宏就具有未定義的行為。
代碼1:
#define CUBE(x) ((x) * (x) * (x))/*...*/int i = 2;int a = 81 / CUBE(++i);
在這個例子中,a的初始設定式展開為: int a = 81/((++i) * (++i) * (++i));
解決方案:
inline int cube(int x){ return x * x *x;}/*...*/int i = 2;int a = 81 / cube(++i);
代碼2:
#include<stdio.h>size_t count = 0;#define EXEC_BUMP(func) (func(), ++count)void g(void) { printf("Called g, count = %zu.\n", count);}void aFunc(void) { size_t count = 0; while(count++ <10) { EXEC_BUMP(g); }}int main(void){ aFunc(); return 0;}
運行結果:
解決方案:
#include<stdio.h>size_t count = 0;void g(void) { printf("Called g, count = %zu.\n", count);}typedef void(*exec_func)(void);inline void exec_bump(exec_func f) { f(); ++count;}void aFunc(void) { size_t count = 0; while(count++ <10) { exec_bump(g); }}int main(void){ aFunc(); return 0;}
運行結果:
和函數不同,宏的執行可以是交錯的,兩個宏單獨執行時無害,但是它們在同一個運算式中組合在一起時可能導致未定義的行為:
代碼3:
#define F(x) (++operations, ++calls_to_F, 2 * x)#define G(x) (++operations, ++calls_to_G, x + 1)/*...*/y = F(x) + G(x);
operations變數在同一個運算式中讀取並修改了2次,因此按照某種順序,可能會接收到錯誤的值
解決方案:
inline int f(int x) { ++operations; ++calls_to_f; return 2 * x;}inline int g(int x) { ++operations; ++calls_to_f; return 1 + x;}/*...*/y = f(x) + g(x);
在宏參數名兩邊加上括弧
代碼1:
#define CUBE(I) (I * I * I)int a = 81 / CUBE(2 + 1)
被展開為: int a = 81 / (2 + 1 * 2 + 1 * 2 + 1);
解決方案:
#define CUBE(I) ((I) * (I) * (I))int a = 81 / CUBE(2 + 1)
例外:當替換文本中的參數名由逗號分隔時,不管實際參數如何複雜,不需要對宏參數加上括弧,因為逗號操作符的優先順序低於其他任何操作符
#define FOO(a, b, c) bar(a, b, c)/*...*/FOO(arg1, arg2, arg3);
宏其他清單應該加上括弧
宏其他清單應該加上括弧,以保護運算式中所有優先順序較低的操作符
代碼1:
#define CUBE(X) (X) * (X) * (X)int i = 3;int a = 81 / CUBE(i);//被展開為: int a = 81 / i * i * i
解決方案:
#define CUBE(X) ((X) * (X) * (X))int i = 3;int a = 81 / CUBE(i);
這個方案最好實現為內嵌函式
應該使用typedef定義編碼類別型
如果需要對類型進行編碼,應該使用類型定義(typedef)而不是宏定義(#define)。類型定義遵循範圍規則,而宏定義卻不遵循
代碼1:
#define cstring char *cstring s1, s2;
其中s1聲明為char *,s2聲明為char
解決方案:
typedef char * cstring;cstring s1, s2;
不要複用標準標頭檔名
如果一個檔案與標準標頭檔同名。並且位於包含源檔案的搜尋路徑中,其行為是未定義的
建議:不要複用標準標頭檔名、系統特定的標頭檔名或其他的標頭檔名
把標頭檔放在包含防護條件中
防止標頭檔沒多次包含或是忘記包含,通過一種簡單的技巧:每個標頭檔應該用#define指令定義一個符號,表示已經被包含,然後整個標頭檔出現在一個包含防護條件中:
#ifndef HEADER_H#define HEADER_H/*....header的內容*/#endif
避免使用連續的問號
兩個連續的問號表示一個三字元序列,據C99標準,在一個源檔案中,下列這些3個字元的連續出現被對應的單個字元所替換
??= |
# |
??) |
] |
??! |
| |
??( |
[ |
??‘ |
^ |
??> |
} |
??/ |
\ |
??< |
{ |
??- |
~ |
代碼1:
//what is the value of a now ??/a++;
由於??/等價於\,a++相當於被注釋掉
解決方案:
//what is the value of a now? ?/a++;
保證標頭檔名唯一
檔案名稱中只有前8個字元保證是唯一的
檔案名稱中的點號後面只有1個非數字字元
檔案名稱中字元的大小寫並不保證是區分的
代碼1:
#include<stdio.h>#include “Library.h”#include "library.h"#include "utilities_math.h"#include "utilities_physics.h"#include "my_library.h"
Library.h和library.h可能表示同一個檔案,並不清楚utilities_math和utilities_physics能否進行區分
解決方案:
#include<stdio.h>#include “Lib_main.h”#include "lib_2.h"#include "util_math.h"#include "util_physics.h"#include "my_library.h"
不要用不安全的函數替換安全函數
宏經常用於修補現有的代碼,用一個標識符對另一個標識符進行全域替換,但是這種做法存在一些風險,當一個函數被一個不夠安全的函數替換時,這種做法就顯得特別的危險
代碼:
#define vsnprintf(buf, size, fmt, list) \ vsprintf(buf, fmt, list)
vsprintf函數並不會檢查邊界,因此size參數將被丟棄,在使用不信任的資料的時候可能會導致潛在的緩衝區溢位問題
解決方案:
#include<stdio.h>#ifndef __USE_ISOC99 /* 重新實現 vsnprintf()*/ #include "my_stdio.h"#endif
在一個do-while迴圈中封裝多條語句的宏
參見《C語言中do...while(0)用法小結》
參考資料
《C安全編碼通訊協定》