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

来源:互联网 发布:固态硬盘克隆软件 编辑:程序博客网 时间:2024/06/05 23:46

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


一、宏常量

    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, @functiondim.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, @functionmain:    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, @functionmain:    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 0
原创粉丝点击