宏定义
函数式宏定义
#define N 20
#define STR ”hello world“
上述的宏定义称为变量式宏定义,宏定义可以像变量一样在代码中使用。另外一种宏定义可以像函数调用一样在代码中使用,称为函数式宏定义。
#define MAX(a,b) (((a)>(b)?(a):(b)
有几点需要强调:
- 函数式宏定义的参数没有类型,预处理只负责做形式上的替换,而不做参数类型检查。
- 调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。如果MAX是一个真正的函数,那么它的函数体要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call命令。而如果MAX是个函数式定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。
- 定义这种宏要格外小心,特别要注意括号,因为宏只是替换展开,没有括号会造成运算优先级的错误。
- 函数式宏定义可以省去分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。例如C标准库的很多函数都提供两种实现,一种式真正的函数实现,一种是宏定义实现。
do { ... } while(0)
对于宏定义中有多行代码的情况,一般使用do { ... } while(0)
括起来,这是为什么呢?其实还是因为宏定义是文本替换,可能会碰到想不到的语法错误。
/*main.c*/
#define PRINT(a, b) \
printf("%d", a); \
printf("%d", b); \
int main(void)
{
int a = 100, b = 200;
if (1)
PRINsT(a, b);
}
我们可以使用GCC命令对上述源代码宏定义展开:
gcc -E -P main.c > main_extend.c
得到如下的文件:
/*main_extend.c*/
int main(void)
{
int a = 100, b = 200;
if (1)
printf("%d", a);
printf("%d", b);
;
}
将源码改成如下:
#define PRINT(a, b) do{ \
printf("%d", a); \
printf("%d", b); \
}while (0)
int main(void)
{
int a = 100, b = 200;
if (1)
PRINT(a, b);
}
展开后的结果就不展示了。
#、##运算符和可变参数
#
字符串话运算符
在函数式宏定义中,#
运算符用于创建字符串,#
运算符后面应该跟一个形参(中间可以有空格或者Tab),例如:
#define PRINT(exp) printf(#exp"= %d\n", exp)
int main(void)
{
PRINT(4 * 5);
}
##
记号粘贴运算符
该运算符会把左、右操作数结合一起,作为一个记号。
#define FOO(a, b, c) a##b##c
int main(void)
{
int a = FOO(1, 2, 3);
printf("%d\n",a);
}
可变参数
在宏定义中,可便参数的部分用__VA_ARGS__
表示,实参中对应...
的几个参数可以看成一个参数替换到宏定义中__VA_ARGS__
所在的地方。
#define PRINT(...) printf(__VA_ARGS__);
int main(void)
{
PRINT("hello world %d!!!\n", 100);
}
#define PRINT(format, ...) printf(format, ##__VA_ARGS__);
int main(void)
{
PRINT("hello world %d!!!\n", 100);
PRINT("hello world !!!\n");
}
当__VA_ARGS
是空参数时,##
运算符把它前面的,
号“吃”掉了
内联函数
虽然函数式宏定义没有了参数压栈、代码生成等一系列操作,但是,在使用它时,仅仅只是左预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也不能享受编译器严格类型检查的好处,另外它的返回值也不能强制转换位可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。内联函数很好的解决此类问题。
关键字inline
必须与函数定义体放在一起才能使函数成为内联,仅将inline
放在函数声明前面不起任何作用。
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
{
}
内联函数是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另外,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
总结:
因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。
C标准特殊宏
__FILE__
:展开为当前源文件的文件名,是一个字符串。
__LINE__
:展开为当前代码行号,是一个整数。
__FUNCTION__
:展开为当前函数名,是一个字符串。