C安全编码--预处理
建議和規(guī)則
建議:
-
用內(nèi)聯(lián)函數(shù)或靜態(tài)函數(shù)代替與函數(shù)相似的宏
-
在宏參數(shù)名兩邊加上括號
-
宏替換列表應(yīng)該加上括號
-
應(yīng)該使用typedef定義編碼類型
-
不要復(fù)用標(biāo)準(zhǔn)頭文件名
-
理解連接標(biāo)記或執(zhí)行字符串化時的宏替換
-
把頭文件放在包含防護(hù)條件中
-
避免使用連續(xù)的問號
-
保證頭文件名唯一
-
不要用不安全的函數(shù)替換安全函數(shù)
-
在一個do-while循環(huán)中包裝多條語句的宏
規(guī)則:
-
不要通過連接創(chuàng)建統(tǒng)一字符名稱
-
不要在不安全宏的參數(shù)中包含賦值、增值、減值、volatile訪問或函數(shù)調(diào)用
本文地址:http://www.cnblogs.com/archimedes/p/c-security-pretreatment-.html,轉(zhuǎn)載請注明源地址。
用內(nèi)聯(lián)函數(shù)或靜態(tài)函數(shù)代替與函數(shù)相似的宏
宏是危險(xiǎn)的,用法與真正的函數(shù)相似,但是具有不同的語義。C99在C中增加了內(nèi)聯(lián)函數(shù),當(dāng)內(nèi)聯(lián)函數(shù)和宏可以互換使用時,應(yīng)該優(yōu)先選擇內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)替換并不是文本替換,也沒有創(chuàng)建函數(shù),決定一個函數(shù)是否為內(nèi)聯(lián)函數(shù)是一個底層的優(yōu)化細(xì)節(jié),編譯器應(yīng)該不依賴程序換做出這個決定,是否使用內(nèi)聯(lián)函數(shù)取決于目標(biāo)編譯器對它們的支持,它們對系統(tǒng)性能特征所產(chǎn)生的影響以及可移植性問題,靜態(tài)函數(shù)常常具有與內(nèi)聯(lián)函數(shù)相同的優(yōu)點(diǎn)。
下面的例子中,當(dāng)傳遞給CUBE宏的參數(shù)是一個具有副作用的表達(dá)式時,這個宏就具有未定義的行為。
代碼1:
#define CUBE(x) ((x) * (x) * (x)) /*...*/ int i = 2; int a = 81 / CUBE(++i);在這個例子中,a的初始化表達(dá)式展開為: 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; }運(yùn)行結(jié)果:
?解決方案:
#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; }運(yùn)行結(jié)果:
和函數(shù)不同,宏的執(zhí)行可以是交錯的,兩個宏單獨(dú)執(zhí)行時無害,但是它們在同一個表達(dá)式中組合在一起時可能導(dǎo)致未定義的行為:
代碼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變量在同一個表達(dá)式中讀取并修改了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);在宏參數(shù)名兩邊加上括號
代碼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)例外:當(dāng)替換文本中的參數(shù)名由逗號分隔時,不管實(shí)際參數(shù)如何復(fù)雜,不需要對宏參數(shù)加上括號,因?yàn)槎禾柌僮鞣膬?yōu)先級低于其他任何操作符
#define FOO(a, b, c) bar(a, b, c) /*...*/ FOO(arg1, arg2, arg3);宏替換列表應(yīng)該加上括號
宏替換列表應(yīng)該加上括號,以保護(hù)表達(dá)式中所有優(yōu)先級較低的操作符
代碼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);這個方案最好實(shí)現(xiàn)為內(nèi)聯(lián)函數(shù)
應(yīng)該使用typedef定義編碼類型
如果需要對類型進(jìn)行編碼,應(yīng)該使用類型定義(typedef)而不是宏定義(#define)。類型定義遵循作用域規(guī)則,而宏定義卻不遵循
代碼1:
#define cstring char * cstring s1, s2;其中s1聲明為char *,s2聲明為char
解決方案:
typedef char * cstring; cstring s1, s2;?不要復(fù)用標(biāo)準(zhǔn)頭文件名
如果一個文件與標(biāo)準(zhǔn)頭文件同名。并且位于包含源文件的搜索路徑中,其行為是未定義的
建議:不要復(fù)用標(biāo)準(zhǔn)頭文件名、系統(tǒng)特定的頭文件名或其他的頭文件名
把頭文件放在包含防護(hù)條件中
防止頭文件沒多次包含或是忘記包含,通過一種簡單的技巧:每個頭文件應(yīng)該用#define指令定義一個符號,表示已經(jīng)被包含,然后整個頭文件出現(xiàn)在一個包含防護(hù)條件中:
#ifndef HEADER_H #define HEADER_H /*....header的內(nèi)容*/ #endif避免使用連續(xù)的問號
兩個連續(xù)的問號表示一個三字符序列,據(jù)C99標(biāo)準(zhǔn),在一個源文件中,下列這些3個字符的連續(xù)出現(xiàn)被對應(yīng)的單個字符所替換
| ??= | # | ??) | ] | ??! | | |
| ??( | [ | ??' | ^ | ??> | } |
| ??/ | \ | ??< | { | ??- | ~ |
代碼1:
//what is the value of a now ??/ a++;由于??/等價(jià)于\,a++相當(dāng)于被注釋掉
解決方案:
//what is the value of a now? ?/ a++;保證頭文件名唯一
-
文件名中只有前8個字符保證是唯一的
-
文件名中的點(diǎn)號后面只有1個非數(shù)字字符
-
文件名中字符的大小寫并不保證是區(qū)分的
代碼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能否進(jìn)行區(qū)分
解決方案:
#include<stdio.h> #include “Lib_main.h” #include "lib_2.h" #include "util_math.h" #include "util_physics.h" #include "my_library.h"不要用不安全的函數(shù)替換安全函數(shù)
宏經(jīng)常用于修補(bǔ)現(xiàn)有的代碼,用一個標(biāo)識符對另一個標(biāo)識符進(jìn)行全局替換,但是這種做法存在一些風(fēng)險(xiǎn),當(dāng)一個函數(shù)被一個不夠安全的函數(shù)替換時,這種做法就顯得特別的危險(xiǎn)
代碼:
#define vsnprintf(buf, size, fmt, list) \vsprintf(buf, fmt, list)vsprintf函數(shù)并不會檢查邊界,因此size參數(shù)將被丟棄,在使用不信任的數(shù)據(jù)的時候可能會導(dǎo)致潛在的緩沖區(qū)溢出問題
解決方案:
#include<stdio.h> #ifndef __USE_ISOC99/* 重新實(shí)現(xiàn) vsnprintf()*/#include "my_stdio.h" #endif在一個do-while循環(huán)中包裝多條語句的宏
參見《C語言中do...while(0)用法小結(jié)》
參考資料
《C安全編碼標(biāo)準(zhǔn)》
?
作者:wuyudong 出處:http://www.cnblogs.com/wuyudong/總結(jié)
以上是生活随笔為你收集整理的C安全编码--预处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言实现多态
- 下一篇: java学习笔记2--数据类型、数组