摘要:总结了宏常量的用法,宏表达式及其与函数的优势对比,宏常量和表达式作用域,最后使用内置宏给出了一种编写使用日志宏的方法。
一、宏常量
1.#define宏常量可以出现在代码的任何地方。
2.#define从本行开始,之后的代码都可以使用这个宏常量。
3.#define宏常量可以使用接续符来定义比较长的常量。
例如以下定义,编译器是不会报错的:
#definePATH3 d:\fedora\c\
myfile.c
等价于:
#definePATH3 d:\fedora\c\myfile.c
二、宏定义表达式
1.#define宏定义表达式有函数调用的假象,但是却不是函数。
2.#define可以比函数更加强大。
3.#define会比函数更加容易出错。
下面给出一个例子,可以解释以上一和二所做的总结,例子如下:
<span style="font-size:18px;">#include <stdio.h>
</span>
<span style="font-size:18px;">#define MAX 99
#define MIN 1
#define SUM(a,b) ((a)+(b))//这里有没有括号在下面的使用有区别
#define DIM(a) (sizeof(a)/sizeof(*a))
#define MESSAGE_1 "This is aexample!\n"
#define MESSAGE_2 This is a examp\
le! //这里使用接续符,编译的时候不会报错,在预处理的时候也会被作为宏定义被去掉
/*fun1中宏定义了SUB,在main函数里面并没有调用fun1,但是我们任然可以使用fun1*/
/*说明#define的作用域是不受限制的,要限制范围,就使用#undef,如下面的的fun2所示*/
/*如果不注释掉#undef那一行,在主函数里面就会报错,说未定义CAL*/
int fun1(void)
{
#defineSUB(c,d) (c)-(d)//这里没有括号,下面SUB*SUB结果就会出现错误
return0;
}
int fun2(void)
{
#defineCAL(m,n) (m)*(n)
// #undefCAL
return0;
}
int main(void)
{
printf("%d\n",SUM(MAX,MIN));
printf("%d\n",SUB(2,1));
printf("%d\n",SUB(2,1)*SUB(2,1));
printf("%d\n",SUM(1,2)*SUM(1,2));
printf("%d\n",CAL(3,4));
printf("mesage1: %s\n",MESSAGE_1);
return0;
}</span>
#gcc –E example.c –o example.i
得到的example.i结果如下:
<span style="font-size:18px;"># 2 "example.c" 2
# 16 "example.c"
int fun1(void)
{
return 0;
}
int fun2(void)
{
return 0;
}
int main(void)
{
printf("%d\n",((99)+(1)));
printf("%d\n",(2)-(1));
printf("%d\n",(2)-(1)*(2)-(1));
printf("%d\n",((1)+(2))*((1)+(2)));
printf("%d\n",(3)*(4));
printf("mesage 1: %s\n","Thisis a example!\n");
return 0;
}</span>
可以看到宏定义已经被全部展开,注释部分也去掉了。
#gcc example.c –o example
运行结果如下:
三、宏定义表达式和函数的优势对比
1.宏表达式在预编译期间被处理,编译器并不知道定义的宏,因为到编译的时候已经被完全替换了。
2.宏表达式用“实参”完全替代形参,不进行任何的计算。
3.宏表达式没有任何的调用开销,因为他不是调用,在使用的时候已经被替换了。
4.宏表达式中不能出现递归定义。
关于上面的规则,下面的例子可以说明一部分,例子如下:
#include<stdio.h>
#define CAL(a) a*CAL(a-1)
#define DIM(a) (sizeof(a)/sizeof(*a))
int main(void)
{
int a[]={1,2,3,4,5};
int dim(int array[])
{
return sizeof(array)/sizeof(*array);
}
// printf("%d\n",CAL(10));注释掉是因为会报错
printf("%d\n",dim(a));//这里打印出来是1,因为函数在传值的时候,进来的不是数组,而是指向数组的指针
printf("%d\n",DIM(a)); //宏表达式就可以出色的完成计算大小的任务
return 0;
}
#gcc –E example2.c –o example2.i
#gcc–S example2.i –o example2.s
example2.s得到的结果如下:
.file "example2.c"
.text
.type dim.1674, @function
dim.1674:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
.size dim.1674, .-dim.1674
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $48, %esp
movl $1, 28(%esp)
movl $2, 32(%esp)
movl $3, 36(%esp)
movl $4, 40(%esp)
movl $5, 44(%esp)
leal 28(%esp), %eax
movl %eax, (%esp)
call dim.1674
movl $.LC0, %edx
movl %eax, 4(%esp)
movl %edx, (%esp)
call printf
movl $.LC0, %eax
movl $5, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.5.1 20100924 (Red Hat4.5.1-4)"
.section .note.GNU-stack,"",@progbits
#gcc example2.c –o example
运行结果如下:
可以看到得到的结果是不一样的,但是宏定义表达式使我们需要的结果。
再说说,为什么宏定义表达式不能使用递归调用,函数如下:
<span style="font-size:18px;">#include <stdio.h>
#define CAL(a) a*CAL(a-1)
int main(void)
{
printf("%d\n",CAL(10));注释掉是因为会报错,原因可以看example2.i文件
return 0;
}</span>
#gcc –E example3.c –o example3.i
#gcc–S example3.i –o example3.s
可以看到展开以后是这样的:
#2 "example2.c" 2
int main(void)
{
printf("%d\n",10*CAL(10 -1));
return 0;
}
这里我们并没有定义CAL,是因为递归展开之后是完全不做计算的展开。
在看看.s文件,
.file "example2.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $9, (%esp)
call CAL
movl %eax, %edx
movl %edx, %eax
sall $2, %eax
addl %edx, %eax
addl %eax, %eax
movl %eax, %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.5.1 20100924 (Red Hat4.5.1-4)"
.section .note.GNU-stack,"",@progbits
红色字体部分是说要调用CAL,我们并没有定义一个CAL函数显然会出现未定义的错误。
四、定义日志宏
首先,内置的宏是可以被用来直接使用的,如下:
我们可以只用这些来定义日志宏,帮助我们调试程序,我认为原因在于,如果你使用函数,存在一个调用开销,这样打印出来的信息,例如行,会是你调用的子函数的信息,而宏是直接替换展开,这样就不存在调用,就是原汁原味的原来的信息,说白了宏定义的核心思想就是“合理的替换”。例子如下:
#include<stdio.h>
#include <time.h>
#define LOG(s) do{ \
time_tt; \
structtm* ti; \
time(&t); \
ti=localtime(&t); \
printf("%s[%s:%d] %s",asctime(ti),__FILE__,__LINE__,s); \
}while(0)
int fun(void)
{
LOG("enterthe fun...\n");
printf("wedo nothing!\n");
LOG("exitthe fun...!\n");
return0;
}
int main()
{
LOG("enterthe main...\n");
fun();
LOG("exitthe main...\n");
return0;
}
编译运行后的结果如下: