• C/C++ 中的宏/Macro


    宏(Macro)本质上就是代码片段,通过别名来使用。在编译前的预处理中,宏会被替换为真实所指代的代码片段,即下图中 Preprocessor 处理的部分。

    C/C++ 代码编译过程

    C/C++ 代码编译过程 - 图片来自 ntu.edu.sg

    根据用法的不同,分两种,Object-like 和 Function-like。前者用于 Object 对象,后者用于函数方法。

    C/C++ 代码编译过程中,可通过相应参数来获取到各编译步骤中的产出,比如想看被预处理编译之后的宏,使用 gcc 使加上 -E 参数。

    $ gcc -E macro.c

    宏的定义

    通过 #define 指令定义一个宏。

    #define NAME_OF_MACRO value

    比如,以下代码定义了一个名为 BUFFER_SIZE 的宏,指代 1024 这个数字。

    #define BUFFER_SIZE 1024

    使用时,

    foo = (char *) malloc (BUFFER_SIZE);

    使用预处理器编译:

    $ gcc -E test.c

    编译结果:

    foo = (char *) malloc (1024);

    多行

    宏的定义是跟随 #define 在一同一行内的,但可通过 反斜杠 实现换行从而定义出多行的宏。

    #include <stdio.h>
    #define GREETING_STR 
      "hello 
    world"
    

    int main() {
    printf(GREETING_STR);
    return 0;
    }

    多行的宏经过编译后会还原到一行中。

    test.c

    #include <stdio.h>
    #define GREETING_STR 
      "hello 
    world"
    

    int main() { printf(GREETING_STR); }

    编译后:

    int main() {
      printf("hello world");
      return 0;
    }

    宏展开时的顺序

    宏的展开是在处理源码时按照其出现位置进行的,如果宏定义有嵌套关系,也是层层进行展开,比如:

    #include <stdio.h>
    

    define GREETING_NAME "wayou"

    define GREETING "hello," GREETING_NAME

    int main() {
    printf(GREETING);
    return 0;
    }

    首先遇到 GREETING,将其展开成 GREETING_NAME "wayou",然后发现另一个宏 GREETING_NAME,将其展开最后得到 "hello," "wayou"。所以编译后的代码为:

    int main() {
      printf("hello," "wayou");
      return 0;
    }

    其展开的顺序并不是宏定义时的顺序,为了验证,可将上面示例代码中两个宏的定义调换一下,得到:

    -#define GREETING_NAME "wayou"
    #define GREETING "hello," GREETING_NAME
    +#define GREETING_NAME "wayou"

    再次编译查看产出,会发现没有区别,也不会报 GREETING 中所依赖的 GREETING_NAME 找不到的错。其实 #define 只是告诉编译器定义了这么个宏,而具体的求值,则是使用宏的地方才开始的。

    像下面这样,当宏存在覆盖时,会以新的为准,其结果为 37。

    #define BUFSIZE 1020
    #define TABLESIZE BUFSIZE
    #undef BUFSIZE
    #define BUFSIZE 37

    Object-like 宏

    Object-like 类型的宏看起来就像普通的数据对象,故名。多用于数字常量的情形下。且宏名一般使用全大写形式方便识别。像上面示例中,都是 Object-like 的。

    Function-like 宏

    也可定义出使用时像是方法调用一样的宏,这便是 Function-like 类型的宏。

    #define lang_init()  c_init()
    lang_init()
    

    // 编译后
    c_init()

    函数类型的宏只在以方法调用形式使用时才会被展开,即名称后加括号,否则会被忽略。当宏名和函数名重名时,这一策略就会显得有用了,比如:

    extern void foo(void);
    #define foo() /* optimized inline version */foo();
      funcptr = foo;

    这里 foo() 的调用会来自宏里面定义的那个函数,而 funcptr 会正确地指向函数地址,如果后者也被宏展开,则成了 funptr=foo() 显然就不对了。

    函数类型的宏在定义时需注意,宏名与后面括号不能有空格,否则就是普通的 Object-like 类型对象。

    #define lang_init ()    c_init()
    lang_init()
    

    // 编译后:
    () c_init()()

    宏的参数

    函数类型的宏,可以像正常函数一样指定入参,入参需为逗号分隔合法的 C 字面量。

    #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));

    入参中的括号

    入参中只需要括号对称,但不要求方括号或花括号成对出现,所以下面的代码:

    macro (array[x = y, x + 1])

    其入参实际为 array[x = yx + 1]

    入参的展开

    入参本质上也是宏,对象类型的宏,在函数宏展示时,这些参数也被展示到了函数宏的函数体里。

     min (min (a, b), c)

    首先被展开成:

    min (((a) < (b) ? (a) : (b)), (c))

    然后进一步展开成(此处换行为方便阅读,实际编译后没有):

    ((((a) < (b) ? (a) : (b))) < (c)
     ? (((a) < (b) ? (a) : (b)))
     : (c))

    参数的缺省

    函数宏在使用时其入参可缺省,但不能全部缺省,至少提供一个入参。

    min(, b)        → ((   ) < (b) ? (   ) : (b))
    min(a, )        → ((a  ) < ( ) ? (a  ) : ( ))
    min(,)          → ((   ) < ( ) ? (   ) : ( ))
    min((,),)       → (((,)) < ( ) ? ((,)) : ( ))
    

    min() error→ macro "min" requires 2 arguments, but only 1 given
    min(,,) error→ macro "min" passed 3 arguments, but takes just 2

    字符化/Stringizing

    如果函数宏中入参在字符串中,是不会被展开的,它就是普通的字符串字面量,这样的结果是符合预期的。

    #define foo(x) x, "x"
    foo(bar)        → bar, "x"

    但如果确实想将入参展开成字符串,可在使用入参时,加上 # 前缀。

    #define WARN_IF(EXP) 
    do { if (EXP) 
            fprintf (stderr, "Warning: " #EXP "
    "); } 
    while (0)
    WARN_IF (x == 0);
         → do { if (x == 0)
               fprintf (stderr, "Warning: " "x == 0" "
    "); } while (0);

    此处 #EXP 在字符串中会被正确展开。What's more, 如果这里的 x 也是宏,那只会在 if 语句中进行展开。

    拼接

    通过 ## 可将两个宏展开成一个,即将两者进行了拼接,这种操作叫 "token pasting",或 "token concatenation",就是拼接嘛。

    宏拼接一般用在需要拼接的宏是来自宏参数的情况,其他情况,大可直接将两个宏写在一起即可,用不着 ## 指令。

    考察下面这个场景,其中命令名重复出现:

    struct command
    {
      char *name;
      void (*function) (void);
    };
    

    struct command commands[] =
    {
    { "quit", quit_command },
    { "help", help_command },

    };

    通过定义宏配合拼接,可达到精简代码的目的:

    #define COMMAND(NAME)  { #NAME, NAME ## _command }
    

    struct command commands[] =
    {
    COMMAND (quit),
    COMMAND (help),

    };

    不定参数

    像普通函数一样,函数类型的宏也可定义接收不定参数。

    #define eprintf(…) fprintf (stderr, __VA_ARGS__)

    调用时,命名参数后面,包括逗号都会进入到 __VA_ARGS__ 关键字当中。但 C++ 中还支持对这些参数命名从而不用 __VA_ARGS__

    eprintf ("%s:%d: ", input_file, lineno)
    

    // 编译后:
    fprintf (stderr, "%s:%d: ", input_file, lineno)

    C++ 中可这么写:

    #define eprintf(args…) fprintf (stderr, args)

    不定参数与命名参数混合的情况

    不定参数为命名参数后面省略的部分。

    #define eprintf(format, …) fprintf (stderr, format, __VA_ARGS__)

    预设的宏

    标准库及编译器中预设了一些有用的宏,可以在这里 查阅。

    取消和重置宏

    当某个宏不再使用时,可通过 #undef 将取注销掉。#undef 后紧跟宏名,后面不要跟其他东西,即使是函数类型的宏。

    #define FOO 4
    x = FOO;        → x = 4;
    #undef FOO
    x = FOO;        → x = FOO;

    两个宏相似的定义

    满足以下条件时,我们认为两者是相似的:

    • 类型相同,比如同为对象类型,或函数类型的宏
    • 展开后各位置的符号(token)相同
    • 如果是函数宏,入参相同
    • 空白的不限但出现的位置相同

    比如,下面这些是相似的:

    #define FOUR (2 + 2)
    #define FOUR         (2    +    2)
    #define FOUR (2 /* two */ + 2)

    而下面这些则不然:

    #define FOUR (2 + 2)
    #define FOUR ( 2+2 ) // 空白位置不一样 
    #define FOUR (2 * 2) // 宏的内容不一样
    #define FOUR(score,and,seven,years,ago) (2 + 2) // 入参不一样

    宏重复定义时的表现

    对于使用了 #undef 注销过的宏,再次定义同名的宏时,要求新定义的宏不与老的相似。

    而如果说一个已经存在的宏,并没有注销,重复定义时,如果相似,则新的定义会忽略,如果不相似,编译器会报警告同时使用新定义的宏。这允许在多个文件中定义同一个宏。

    相关资源

  • 相关阅读:
    用 pytube 爬取 youtube 视频
    Ubuntu W: GPG error: http://archive.ubuntukey....NO_PUBKEY 8D5A09
    windows 下 putty 登陆服务器 显示matlab图形界面
    python27 windows 下三种安装第三方库的办法
    MCMC: The Metropolis-Hastings Sampler
    MCMC: The Metropolis Sampler
    Latex 数学符号
    差点被坑
    新年新flag
    听报告为什么要问问题
  • 原文地址:https://www.cnblogs.com/Wayou/p/macros_in_c_and_cpp.html
Copyright © 2020-2023  润新知