Administrator
Published on 2021-12-04 / 186 Visits
0

宏#define

概念

  • C语言中,可以用 #define 定义一个标识符来表示一个常量
    定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。

预编译又叫预处理。预编译不是编译,而是编译前的处理。这个操作是在正式编译之前由系统自动完成的。

凡是以“#”开头的均为预处理指令,#define也不例外,此外还有 #include等。

  • 宏所表示的常量可以是数字、字符、字符串、表达式。
  • 宏名表示的是一个常量,不能给常量赋值。
  • 为了将标识符与变量名区别开来,习惯上标识符全部用大写字母表示。
  • 重复定义宏只有最后一个生效

如果两个宏定义之间,仅有空格和注释不同的话,两个宏定义还是同一个宏定义。

类型

  • Object-like宏
    如写法中的一般写法
  • Function-like宏
    如写法中的函数宏

写法

  • 一般写法
    #define 标识符 常量

#define又称宏定义,标识符为所定义的宏名,简称宏。

注意,最后没有分号,预处理指令不是语句,所以后面不能加分号。

宏定义 #define 一般都写在函数外面,与 #include 写在一起。写在函数里面也没有语法错误,但通常不那么写。

  • 终止作用域
    #undef 标识符

#define 的作用域为自 #define 那一行起到源程序结束。如果要终止其作用域可以使用 #undef 命令,undef 后面的标识符表示你所要终止的宏,如果在#undef之后再使用对应的标识符就会报错,没有定义对应的标识符。

  • 多行宏
    #define NUMBERS 1, \
    2, \
    3
  • 多次宏替换
    #define TABLESIZE BUFSIZE
    #define BUFSIZE 1024

如果宏定义的代码段依然是宏的话,预处理器会继续进行宏替换的操作。TABLESIZE的最终的值就是1024。

  • 函数宏
    #define lang_init() c_init() //普通
    #define min(X, Y) ((X) < (Y) ? (X) : (Y)) //带参数
    #define str(expr) printf("%s\r\n", #expr) //字符串化

字符串化指的是,可以在宏的参数前面加入#,使入参变成字符串。

#define myprintf(...) fprintf (stderr, __VA_ARGS__) //可变参数

这种形式的宏,会把…的代表的参数扩展到后面的__VA_ARGS__中。

#define myprintf(args...) fprintf (stderr, args) //可变参数

  • 用两个#将两个符号连接成一个符号
    #define A(NAME) A##NAME

  • 预定义宏
    printf("DATA:%s\r\n",__DATE__); //打印日期

预定义宏有标准预定义宏/GNU C编译器扩展实现 / 不同系统特定的预定义宏, 用双下划线开头和结尾,例如__FILE__和__LINE__

好处

  • 使用宏定义可以用宏代替一个在程序中经常使用的常量,方便修改。
  • 当常量比较长时,使用宏就可以用较短的有意义的标识符来代替它。

注意

可以将任意的有效的标识符定义为宏,C语言的关键字也可以。
但是在C语言中"defined"不可以作为宏的名称。
在C++中以下的关键字也不可以作为宏的名称:and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq,因为在C++ 中系统将这些关键字预定义成了操作符。

在C++ 中,你可以使用命名操作符来代替这些符号,比如int c = a bitor b;等同int c = a | b;

预处理

其实预编译所执行的操作就是简单的“文本”替换。对宏定义而言,预编译的时候会将程序中所有出现“标识符”的地方全部用这个“常量”替换,称为“宏替换”或“宏展开”。替换完了之后再进行正式的编译。
所以说当单击“编译”的时候实际上是执行了两个操作,即先预编译,然后才正式编译。
#include<stdio.h>也是这样的,即在预处理的时候先单纯地用头文件stdio.h中所有的“文本”内容替换程序中#include<stdio.h>这一行,然后再进行正式编译。

用宏来实现日志功能

#define LOG(str) \
do \
{\
    fprintf(stderr, "[%s:%d %s %s]:%s\r\n",  __FILE__, __LINE__, __DATE__, __TIME__, str); \
}while(0)

int main()
{
   LOG("malloc for buf");
   return 0;
}

看似这个do() while(0)没有什么意义。但是这是一个编写宏内多行代码段的好习惯。使用do{}while(0)包含的话,可以作为一个独立的block,进行变量定义等一些复杂的操作,该用法主要是防止在使用宏的过程中出现错误。具体看参考对应的链接。

连接符号的用法

#include <stdio.h>
#define A1 printf("print A1\r\n")
#define A2 printf("print A2\r\n")
#define A(NAME) A##NAME
int main()
{
    A(1);	//打印print A1
    return 0;
}

如以上例子,输入1,通过##连接符号就变成了A1,而A1刚好有一个函数宏A1,所以最终就调用了A1这个函数宏。
这种写法可以通过根据不同的传参调用不同的函数宏。

宏在系统中的使用样例

比如offsetof,container_of等,具体看参考里的链接。

参考

C语言宏的用法详解
https://blog.csdn.net/armlinuxww/article/details/93965786
#define用法,C语言#define详解
http://m.biancheng.net/view/187.html