记录下宏的用法

Object-like 宏

这种比较简单

Function-like 宏

宏的参数


#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
  x = min(a, b);          //→  x = ((a) < (b) ? (a) : (b));
  y = min(1, 2);          //→  y = ((1) < (2) ? (1) : (2));
  z = min(a + 28, *p);    //→  z = ((a + 28) < (*p) ? (a + 28) : (*p));

用参数的话,后面定义的参数都要带上括号,不然会有二义性。

字符串化

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

eg

#include <stdio.h>
#define str(expr) printf("%s\r\n", #expr)
 
int main()
{
    str(abc);
    str(12345);
    return 0;
}
 
这里运行代码会打印:
 
abc
12345
 
str宏的入参,都变成了字符串打印了出来。

连接符号

在宏中,可以使用两个#将两个符号连接成一个符号。
#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);
    return 0;
}
 
这里会打印
 
print A1

在该例子中,调用宏A(1)时,NAME为1。A##NAME这个符号连接,即将A和1连接成了一个符号A1,然后执行宏A1的内容。最终打印出来了print A1

可变参数

定义宏可以接受可变数量的参数,类似于定义函数一样。如下就是一个例子
#include <stdio.h>
#define myprintf(...) fprintf (stderr, __VA_ARGS__)
int main()
{
    myprintf("1234\r\n");
    return 0;
}
 
这里会输出
 
1234

这种形式的宏,会把…的代表的参数扩展到后面的__VA_ARGS__中。在该例子中,就会扩展为fprintf(stderr, “1234\r\n”)。
如果你的参数比较复杂,上面的myprintf还可以定义为如下的形式,用自定义的名称args来表示参数的含义:

#define myprintf(args...) fprintf (stderr, args)

预定义宏

标准预定义宏
标准的预定义宏都是用双下划线开头和结尾,例如__FILE__和__LINE__,表示文件的名称和该行代码的行号。

#include <stdio.h>
 
int main()
{
    printf("FILE:%s,LINE:%d\r\n",__FILE__, __LINE__);
    printf("DATA:%s\r\n",__DATE__);
    printf("TIME:%s\r\n",__TIME__);
    printf("STDC:%d\r\n",__STDC__);
    printf("STDC_VERSION:%d\r\n",__STDC_VERSION__);
    printf("STDC_HOSTED:%d\r\n",__STDC_HOSTED__);
#ifdef __cplusplus
    printf("cplusplus:%d\r\n", __cplusplus);    
#else
    printf("complied by c\r\n");    
#endif
    
    return 0;
}
 
输出如下
 
FILE:macro.c,LINE:5
DATA:Jan 13 2019
TIME:21:41:14
STDC:1
STDC_VERSION:201112
STDC_HOSTED:1
complied by c
 
 
本文件名为macro.c,并且该行代码为第5行。
__DATA__表示当前的日期
__TIME__表示当前的时间
__STDC__在正常的操作中,此宏为1,表示编译器符合ISO C标准
__STDC_VERSION__表示ISO C的版本
__STDC_HOSTED__如果值为1的话,表示目标环境有完成的标准C库
__cplusplus如果该宏被定义了,表示是被C++编译器编译的
常见的预定义宏 该节中的宏是GNU C编译器的扩展实现。
#include <stdio.h>
 
int main()
{
    printf("__COUNTER_%d\r\n", __COUNTER__);
    printf("__COUNTER_%d\r\n", __COUNTER__);    
    printf("__GNUC__:%d\r\n",__GNUC__);
    printf("__GNUC_MINOR__:%d\r\n",__GNUC_MINOR__);
    printf("__GNUC_PATCHLEVEL__:%d\r\n",__GNUC_PATCHLEVEL__);
    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
        printf("little endian\r\n");
    #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        printf("big endian\r\n");
    #elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__
        printf('pdp endian\r\n')
    #endif
    #if __LP64__ == 1
        printf("64bit env\r\n");
    #else
        printf("other bit env\r\n");
    #endif
    return 0;
}
 
输出
 
__COUNTER_0
__COUNTER_1
__GNUC__:7
__GNUC_MINOR__:3
__GNUC_PATCHLEVEL__:0
little endian
64bit env
 
 
__COUNTER_:是生成一个唯一的数字。
__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__确定了你的GCC版本号。例如我的环境就是7.3.0
__BYTE_ORDER__表示当前环境的字节序
__LP64__ 表示当前环境是不是64位,如果该值为1,则环境为64位环境
更多GNU C编译器的预定义宏可以 点此连接查看
系统特定的预定义宏 系统特定的预定义宏,在不同的操作系统和CPU上面,呈现的结果可能会有所不同。例如我的环境是Linux X86_64平台。执行下面的代码
#include <stdio.h>
 
int main()
{
    printf("__unix_:%d\r\n", __unix__);
    printf("__x86_64__:%d\r\n", __x86_64__);
    return 0;
}
 
输出结果是:
 
__unix_:1
__x86_64__:1
 
如果是其他操作系统的CPU平台的话,执行的结果会有所不同。

C++的命名操作符 在第一节就说过C++ 中有and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq这些命名不可以用作宏的名称。是因为在C++ 中系统将这些关键字预定义成了操作符。

命名操作符    符号
and    &&
and_eq    &=
bitand    &
bitor    |
compl    ~
not    !
not_eq    !=
or    ||
or_eq    |=
xor    ^
xor_eq    ^=
所以在C++ 中,你可以使用命名操作符来代替这些符号。例如:
 
#include <iostream>
using namespace std;
int main()
{
    int a = 10;
    int b = 20;
    int c = a bitor b; // a | b
    int d = a bitand b; //a & b
    cout << "c = " << c << endl;
    cout << "d = " << d << endl;
 
    if ( true and (a > b))
        cout << "true" << endl;
    else
        cout << "false" << endl;
        
    return 0;
}
 
 
输出:
 
c = 30
d = 0
false

取消宏定义与重复宏定义

取消宏定义 使用#undef可以将已经定义的宏取消掉

#define BUFSIZE 1020
#undef BUFSIZE

如果宏重复的话,编译器会给出宏重复定义的警告。也只有最后一个宏才会生效。

do{}while(0)

用do while 0 来代表代码块。

#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 1024
#define LOG(str) \
do \
{\
    fprintf(stderr, "[%s:%d %s %s]:%s\r\n",  __FILE__, __LINE__, __DATE__, __TIME__, str); \
}while(0)
int main()
{
    char *buf = (char *)malloc(BUFSIZE);
    LOG("malloc for buf");
    free(buf);
    return 0;
}

使用do{}while(0)包含的话,可以作为一个独立的block,进行变量定义等一些复杂的操作
该用法主要是防止在使用宏的过程中出现错误。


#include <stdio.h>
#include <stdlib.h>
#define add(x, y) {x += 1; y += 2;}
 
int main()
{
    int x = 10;
    int y = 20;
    if (x > y)
        add(x, y);
    else
        ;
 
    return 0;
}

这里在add(x, y)之后有个分号。会造成else匹配不到if编译错误。所以为了防止发生这些错误,可以使用do{}while(0)将函数体包含。

Linux内核中offsetof

在Linux的内核代码中,大量的使用到了offsetof这个宏,该宏的作用就是计算出一个结构体中的变量的偏移值是多少。

#include <stdio.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
typedef struct myStructTag
{
    int a;
    double b;
    float c;
    char szStr[20];
    long int l;
}myStruct;
int main()
{
    printf("%d\r\n", offsetof(myStruct, a));
    printf("%d\r\n", offsetof(myStruct, b));
    printf("%d\r\n", offsetof(myStruct, c));
    printf("%d\r\n", offsetof(myStruct, szStr));
    printf("%d\r\n", offsetof(myStruct, l));
}

Linux内核中container_of宏

该宏的作用就是通过结构体任意成员的地址来获取结构体指针。该宏需要借助上一节的offsetof。
下面是使用该宏的代码:

#include <stdio.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
    const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })
 
typedef struct myStructTag
{
    int a;
    double b;
    float c;
    char szStr[20];
    long int l;
}myStruct;
int main()
{
    myStruct *p = (myStruct *)malloc(sizeof(myStruct));
    printf("base ptr=%p\r\n", p);
    printf("base ptr by l=%p\r\n", container_of(&p->l, myStruct, l));
}
 
输出内容:
 
base ptr=0x55cc10d66260
base ptr by l=0x55cc10d66260
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值