程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C安全編碼--預處理

C安全編碼--預處理

編輯:C++入門知識

建議和規則

建議:

  • 用內聯函數或靜態函數代替與函數相似的宏

  • 在宏參數名兩邊加上括號

  • 宏替換列表應該加上括號

  • 應該使用typedef定義編碼類型

  • 不要復用標准頭文件名

  • 理解連接標記或執行字符串化時的宏替換

  • 把頭文件放在包含防護條件中

  • 避免使用連續的問號

  • 保證頭文件名唯一

  • 不要用不安全的函數替換安全函數

  • 在一個do-while循環中包裝多條語句的宏

規則:

  • 不要通過連接創建統一字符名稱

  • 不要在不安全宏的參數中包含賦值、增值、減值、volatile訪問或函數調用

本文地址: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安全編碼標准》

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved