C安全編碼 預處理

2021-07-10 15:57:25 字數 4157 閱讀 1345

建議:

規則:巨集是危險的,用法與真正的函式相似,但是具有不同的語義。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)/*

...*/

int i = 2

;int a = 81 / cube(++i);

**2:

#includesize_t count = 0

;#define exec_bump(func) (func(), ++count)

void g(void

) void afunc(void) }

int main(void

)

執行結果:

解決方案:

#includesize_t count = 0

;void g(void

) typedef

void(*exec_func)(void

);inline

void

exec_bump(exec_func f)

void afunc(void) }

int main(void

)

執行結果:

和函式不同,巨集的執行可以是交錯的,兩個巨集單獨執行時無害,但是它們在同乙個表示式中組合在一起時可能導致未定義的行為:

**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) inline

int g(int

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)而不是巨集定義(#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++;

**1:

#include#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#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#ifndef __use_isoc99

/*重新實現 vsnprintf()

*/#include

"my_stdio.h

"#endif

參見《c語言中do...while(0)用法小結

》《c安全編碼標準》

wuyudong

出處:

c 預處理和預處理命令

預處理發生在編譯之前,預處理輸出的是乙個單一的檔案,這個檔案被送到編譯器,進行編譯。每條預處理命令都控制預處理器的行為。每條預處理命令佔據一行,有以下的格式 character 預處理命令 one of define,undef,include,if,ifdef,ifndef,else,elif,e...

C 預處理命令

c 提供的預處理功能 巨集定義 檔案包含和條件編譯 分別由巨集定義命令 檔案包含命令和條件編譯命令三種預處理命令來實現。預處理命令 格 式 預處理命令 末尾不加分號 作用域 從定義點到程式結束 說 明 預處理命令獨佔一行,位置任意 巨集定義命令 格 式 define 巨集名 形參 巨集體 undef...

C 預處理指令

1.define 通常和 if一起使用 使用 define可以定義乙個符號,並通過將該符號用作表示式傳遞給 if 指令,使該表示式的計算結果為true 比如 preprocessor if.cs define debug define vc v7 using system public class ...