c语言学习笔记(11)宏定义使用与分析

摘要:总结了宏常量的用法,宏表达式及其与函数的优势对比,宏常量和表达式作用域,最后使用内置宏给出了一种编写使用日志宏的方法。


一、宏常量

    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;
 
} 

    编译运行后的结果如下:

    这篇帖子就总结到这里吧,如有不正确的地方还请指出,大家共同进步!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值