预定义宏

__DATE__、__FILE__、__LINE__、__TIME__、__func__

其实预定义宏不止这些,不过这里我们就只介绍这些


一、什么是预定义宏

预定义宏,也可以称为编译器内置宏,这个宏并没有定义在哪个.h文件中,所以不能再哪个.h中找到这些玩意,
进行预编译时,当预编译器看到这些玩意时,会自动处理这些预定义宏


二、作用

1.__DATE__:代表预处理的日期
当预处理器检测到__DATE__后,会将其替换为"月 日 年"的字符串形式的时间,时间格式是西方人习惯的格式。

2.__FILE__:代表当前预编译正在处理的那个源文件的文件名
当预处理器检测到__FILE__后,会将其替换为"***.c"的文件名。

3.__LINE__:代表__LINE__当前所在行的行号
当预处理器检测到__LINE__后,会将其替换为__LINE__当前所在行的行号(整形)。

4.__TIME__:代表对源文件进行预编译时的时间
当预处理器检测到__TIME__后,会将其替换为“hh:mm:ss”格式的时间。

5.__func__:当前__func__所在函数的函数名
不过这个在预编译阶段不会被处理,而是留到编译阶段处理。

6.例子
helloworld.c

#include <stdio.h>

int main(void)
{
    printf("预编译的日期:%s\n", __DATE__);
    printf("预编译的文件:%s\n", __FILE__);
    printf("当前所在行号:%d\n", __LINE__);
    printf("预编译的时间:%s\n", __TIME__);
    printf("当前所在函数:%s\n", __func__);

    return 0;
}

查看预编译结果:

int main(void)
{
     printf("预编译的日期:%s\n", "Jul  5 2018");  //替换为了日期
     printf("预编译的文件:%s\n", "helloworld.c"); //替换为了文件名
     printf("当前所在行号:%d\n", 7);              //替换为了__LINE__所在的行号
     printf("预编译的时间:%s\n", "02:21:08");     //替换为了预编译的时间
     printf("当前所在函数:%s\n", __func__);       //没有替换,到第二阶段编译时再处理,__func__代表的函数名是main

     return 0;
}

打印结果:

预编译的日期:Jul  5 2018
预编译的文件:helloworld.c
当前所在行号:7
预编译的时间:02:28:15
当前所在函数:main

除了__func__外,其它几个都可以定义相应的变量来存放,比如:

int line = __LINE__;
char date[] = __DATE__;

三、预定义宏的意义 与 调试宏

1. 意义

常常用于调试打印、跟踪代码用

当一个程序写大了后,在调试程序的功能性错误时,往往需要打印信息来跟踪代码,
看看程序是运行到什么位置时才出现了功能性错误,以方便我们调试

printf("%s %d %s\n", __FILE__, __LINE__, __func__);

当然如果不需要显示文件名和函数名的话,其实直接打印行号就可以了

printf("%d\n", __LINE__);

2. 调试宏

在每个需要打印的地方都写printf会非常的麻烦,因此我们可以把它写成调试宏。

1)例子:
#include <stdio.h>

//调试宏,DEBUG的名字可以自己随便起
#define DEBUG printf("%s %d %s\n", __FILE__, __LINE__, __func__);

void exchange(int *p1, int *p2)
{      
    DEBUG
    int tmp = 0;
    DEBUG

    tmp = *p1;
    DEBUG
    *p1 = *p2;
    DEBUG
    *p2 = tmp;
    DEBUG
}

int main(void)
{       
    int a = 10; 
    int b = 30; 

    DEBUG
    exchange(&a, &b);
    DEBUG

    printf("a=%d, b=%d\n", a, b);

    DEBUG

    return 0;
}

通过打印信息来跟踪程序,其实有些时候比“单步运行调试”更好用,
因为单步运行调试在某些情况其实很麻烦,不如打印信息来的好使

2)如果你想打印自定义信息的话,我们还可以将调试宏定义为带参宏
#define DEBUG(s1, s2) printf(s1, s2);

在程序中使用时,可以指定你要任何打印的调试信息。

DEBUG("%d\n", va); //你想写什么,可以由你自己决定。
DEBUG("%s\n", str);

疑问:感觉这也不比直接写printf("%d\n", va)更方便呀?

不直接使用printf,而是写成DEBUG(s1, s2)带参宏的形式,
可以方便我们使用“条件编译”来快速打开和关闭调试宏,后面将再介绍这个问题