天天看点

C安全编码--预处理

建议:

用内联函数或静态函数代替与函数相似的宏

在宏参数名两边加上括号

宏替换列表应该加上括号

应该使用typedef定义编码类型

不要复用标准头文件名

理解连接标记或执行字符串化时的宏替换

把头文件放在包含防护条件中

避免使用连续的问号

保证头文件名唯一

不要用不安全的函数替换安全函数

在一个do-while循环中包装多条语句的宏

规则:

不要通过连接创建统一字符名称

不要在不安全宏的参数中包含赋值、增值、减值、volatile访问或函数调用

  宏是危险的,用法与真正的函数相似,但是具有不同的语义。c99在c中增加了内联函数,当内联函数和宏可以互换使用时,应该优先选择内联函数,

内联替换并不是文本替换,也没有创建函数,决定一个函数是否为内联函数是一个底层的优化细节,编译器应该不依赖程序换做出这个决定,是否使用内联函数取决

于目标编译器对它们的支持,它们对系统性能特征所产生的影响以及可移植性问题,静态函数常常具有与内联函数相同的优点。

下面的例子中,当传递给cube宏的参数是一个具有副作用的表达式时,这个宏就具有未定义的行为。

代码1:

在这个例子中,a的初始化表达式展开为: int a = 81/((++i) * (++i) * (++i));

解决方案:

代码2:

运行结果:

C安全编码--预处理

 解决方案:

C安全编码--预处理

和函数不同,宏的执行可以是交错的,两个宏单独执行时无害,但是它们在同一个表达式中组合在一起时可能导致未定义的行为:

代码3:

operations变量在同一个表达式中读取并修改了2次,因此按照某种顺序,可能会接收到错误的值

被展开为: int a = 81 / (2 + 1 * 2 + 1 * 2 + 1);

例外:当替换文本中的参数名由逗号分隔时,不管实际参数如何复杂,不需要对宏参数加上括号,因为逗号操作符的优先级低于其他任何操作符

宏替换列表应该加上括号,以保护表达式中所有优先级较低的操作符

这个方案最好实现为内联函数

如果需要对类型进行编码,应该使用类型定义(typedef)而不是宏定义(#define)。类型定义遵循作用域规则,而宏定义却不遵循

其中s1声明为char *,s2声明为char

如果一个文件与标准头文件同名。并且位于包含源文件的搜索路径中,其行为是未定义的

建议:不要复用标准头文件名、系统特定的头文件名或其他的头文件名

防止头文件没多次包含或是忘记包含,通过一种简单的技巧:每个头文件应该用#define指令定义一个符号,表示已经被包含,然后整个头文件出现在一个包含防护条件中:

两个连续的问号表示一个三字符序列,据c99标准,在一个源文件中,下列这些3个字符的连续出现被对应的单个字符所替换

??=

#

??)

]

??!

|

??(

[

??'

^

??>

}

??/

\

??<

{

??-

~

由于??/等价于\,a++相当于被注释掉

文件名中只有前8个字符保证是唯一的

文件名中的点号后面只有1个非数字字符

文件名中字符的大小写并不保证是区分的

library.h和library.h可能表示同一个文件,并不清楚utilities_math和utilities_physics能否进行区分

解决方案:

宏经常用于修补现有的代码,用一个标识符对另一个标识符进行全局替换,但是这种做法存在一些风险,当一个函数被一个不够安全的函数替换时,这种做法就显得特别的危险

代码:

vsprintf函数并不会检查边界,因此size参数将被丢弃,在使用不信任的数据的时候可能会导致潜在的缓冲区溢出问题

继续阅读